17. Udostępnianie bazy danych w sieci
17.1. Architektura serwisu internetowego / jSON
Wdrożymy następującą architekturę:
![]() |
- w [1] warstwy [DAO, [JPA], JDBC] są zaimplementowane przez jedną z 24 konfiguracji przedstawionych w poprzednich rozdziałach, a w szczególności w paragrafie 15;
- warstwa [DAO] [3] klienta zdalnego implementuje ten sam interfejs co warstwa [DAO] [1], co pozwala nam na wykorzystanie tej samej warstwy testowej, co w poprzednich rozdziałach. Wszystko przebiega tak, jakby warstwy [2-3] były przezroczyste dla warstwy [4];
Będziemy opierać się na następujących projektach:
- projekt [sgbd-config-jdbc], który konfiguruje warstwę JDBC jednej z sześciu warstw SGBD;
- projekt [sgbd-config-jpa-*], który konfiguruje warstwę JPA w ramach wybranego SGBD dla jednej z trzech badanych implementacji JPA (Hibernate, EclipseLink, OpenJpa);
- ogólny projekt [spring-jdbc-04], który implementuje warstwę [DAO] [1];
- projekt generyczny [spring-jpa-generic], który implementuje warstwę [DAO] oraz [2];
- projekt generyczny [spring-webjson-server-jdbc-generic], który implementuje usługę internetową opartą na projekcie [spring-jdbc-04];
- projekt generyczny [spring-webjson-server-jpa-generic], który implementuje usługę internetową opartą na projekcie [spring-jpa-generic];
- klient generyczny [spring-webjson-client-generic], który będzie jedynym klientem dla wszystkich 24 konfiguracji usługi internetowej;
17.2. Przygotowanie środowiska pracy
Będziemy pracować z następującymi elementami:
- SGBD MySQL 5.6.25;
- wdrożenie JPA Hibernate;
Zaimportuj następujące projekty do STS:
![]() |
- projekty [spring-webjson-*] znajdują się w folderze [<exemples>\spring-database-generic\spring-webjson];
- wykonaj [Alt-F5], a następnie zregeneruj wszystkie powyższe projekty;
Aby sprawdzić, czy środowisko pracy zostało poprawnie zainstalowane, wykonaj następujące czynności:
- uruchom usługę internetową z konfiguracją uruchomieniową [spring-webjson-server-jpa-generic-hibernate], która opiera się na implementacji JPA / Hibernate;
![]() | ![]() |
następnie:
- uruchom klienta tej usługi internetowej z konfiguracją uruchomieniową [spring-webjson-client-generic], która jest testem JUnit:
![]() | ![]() |
Test powinien zakończyć się powodzeniem:
![]() |
- w [1] należy zatrzymać usługę internetową, a następnie uruchomić ją z konfiguracją wykonania [spring-webjson-server-jdbc-generic], która opiera się na implementacji JDBC:
![]() | ![]() |
następnie uruchom klienta tej usługi internetowej z konfiguracją wykonania [spring-webjson-client-generic]:
![]() | ![]() |
Test powinien zakończyć się powodzeniem:
![]() |
17.3. Wdrożenie usługi internetowej / jSON / JDBC
W pierwszej kolejności skupiamy się na następującej architekturze:
![]() |
gdzie warstwa [DAO] [1] komunikuje się bezpośrednio z warstwą JDBC z SGBD.
17.3.1. Projekt Eclipse serwisu internetowego
Projekt Eclipse usługi internetowej / jSON / JDBC wygląda następująco:
![]() |
Jest to projekt Maven, którego plik [pom.xml] wygląda następująco:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-webjson-server-jdbc-generic</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-webjson-server-jdbc-generic</name>
<description>démo spring mvc</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- warstwa internetowa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- warstwa [DAO] -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-jdbc-generic-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- wtyczki -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- wiersze 11–15: nadrzędny projekt Maven;
- wiersze 24–28: zależność od warstwy [DAO / JDBC] zaimplementowanej przez projekt [spring-jdbc-generic-04];
- wiersze 19–22: zależność od artefaktu [spring-boot-starter-web]. Artefakt ten zawiera wszystkie zależności niezbędne do utworzenia serwisu internetowego / jSON. Zawiera on również zbędne biblioteki. Konieczna byłaby zatem bardziej precyzyjna konfiguracja, ale ta konfiguracja jest praktyczna na początek.
Zależności wynikające z tej konfiguracji są następujące:
![]() | ![]() | 1 ![]() |
- w pliku [1] widać, że Eclipse wykryło zależność od archiwum projektu [spring-jdbc-generic-04];
Powyższe zależności dotyczą zarówno warstwy [DAO], jak i warstwy [web].
17.3.2. Konfiguracja warstwy [web]
Warstwa [web] jest konfigurowana za pomocą dwóch plików konfiguracyjnych Spring:
![]() |
17.3.2.1. Klasa [WebConfig]
Głównym zadaniem klasy [WebConfig] jest konfiguracja:
- serwer Tomcat, na którym zostanie wdrożona usługa internetowa;
- filtry jSON służące do serializacji i deserializacji obiektów [Produit] oraz [Categorie]:
package spring.webjson.server.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- konfiguracja warstwy [web]
@Autowired
private ApplicationContext context;
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet=new DispatcherServlet((WebApplicationContext) context);
return servlet;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new ServletRegistrationBean(dispatcherServlet, "/*");
}
@Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory("", 8081);
}
// -------------------------------- konfiguracja filtrów [json]
...
}
- wiersz 25: klasa ta jest klasą konfiguracyjną Springa;
- wiersz 26: adnotacja [@EnableWebMvc] wskazuje, że warstwa internetowa jest zaimplementowana przy użyciu Springa MVC. Spowoduje to domyślne konfiguracje, których nie będziemy musieli wprowadzać;
- wiersze 30–31: wstrzyknięcie kontekstu Springa aplikacji;
- wiersze 33–37: definicja bean’a [dispatcherServlet], który w aplikacjach Spring MVC pełni rolę [FrontController], którego zadaniem jest kierowanie żądań klientów do kontrolera zdolnego do ich przetworzenia;
- wiersze 39–42: rejestrowany jest serwlet usługi internetowej wraz z URL, które obsługuje. W tym miejscu wpisano [/*], co oznacza wszystkie URLs;
- wiersze 44–47: definicja komponentu [embeddedServletContainerFactory], który określa serwer WWW, z którego ma być korzystano. W tym przypadku będzie to serwer WWW Tomcat [http://tomcat.apache.org/]. Można również użyć serwera Jetty o nazwie [http://www.eclipse.org/jetty/]. Oba serwery są wbudowane w zależnościach Maven. Gdy [Spring Boot] steruje uruchomieniem projektu, automatycznie uruchamia serwer WWW wskazany w konfiguracji i wdraża na nim usługę lub aplikację internetową;
Konfiguracja filtrów jSON odbywa się w następujący sposób:
package spring.webjson.server.config;
import java.util.List;
...
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- konfiguracja warstwy [web]
...
// -------------------------------- konfiguracja filtrów [json]
// mapowanie jSON
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
converter.setObjectMapper(objectMapper);
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
// filtry jSON
@Bean
public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
return mappingJackson2HttpMessageConverter.getObjectMapper();
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperShortProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperLongProduit(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
return jsonMapper;
}
}
- wiersz 8: klasa [WebConfig] dziedziczy po klasie [WebMvcConfigurerAdapter]. Ta ostatnia klasa konfiguruje aplikację internetową przy użyciu wartości domyślnych. Jeśli chcemy dostosować tę konfigurację, należy przedefiniować niektóre metody tej klasy. W tym przypadku chcemy przedefiniować metodę [configureMessageConverters] klasy [22-26] (zwróć uwagę na adnotację @Override), która definiuje listę „konwerterów”. Serwis internetowy /jSON i jego klient wymieniają między sobą wiersze tekstu. Konwerter to narzędzie zdolne do utworzenia obiektu na podstawie otrzymanego wiersza tekstu (deseryalizacja) oraz do utworzenia wiersza tekstu na podstawie obiektu (serializacja). W tym przypadku wierszami tekstowymi będą ciągi znaków jSON. Będziemy więc mówić o serializacji/deserializacji jSON;
- wiersz 23: metoda [configureMessageConverters] przyjmuje jako parametr listę konwerterów;
- wiersze 24–25: do tej listy dodano konwerter jSON [MappingJackson2HttpMessageConverter] z wierszy [14-20]. Umożliwi to wymianę danych jSON między klientem a serwerem;
- wiersze [14-20]: definiują konwerter jSON zaimplementowany przez klasę [MappingJackson2HttpMessageConverter]. Klasę tę (wiersz 10) można znaleźć w zależnościach Maven projektu;
- wiersze [17-18]: tworzony jest mapper jSON i przypisywany do konwertera [MappingJackson2HttpMessageConverter];
- wiersze [29-32]: definiują mapper jSON utworzony w wierszu 17 jako bean Spring. Dzięki temu trafia on do kontekstu Spring i staje się dostępny do wstrzyknięcia do innych beanów lub wykorzystania w kodzie aplikacji internetowej;
- wiersze 34–41: definiują filtr jSON dla poprzedniego mapera jSON;
- linia 35: adnotacja [@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)] sprawia, że zdefiniowany tutaj bean nie jest singletonem. Za każdym razem, gdy zostanie on zażądany z kontekstu, metoda [jsonMapperCategorieWithoutProduits] zostanie ponownie wykonana. Jest to tutaj konieczne, ponieważ definiujemy cztery filtry jSON. Jednak w danym momencie aktywny może być tylko jeden. Nadając beanowi zakres [ConfigurableBeanFactory.SCOPE_PROTOTYPE], mamy pewność, że metoda zostanie ponownie wykonana, a poprzedni filtr zostanie zastąpiony nowym;
- aby zrozumieć te filtry, należy pamiętać, że w warstwie [DAO]:
- entyteta [Produit] została oznaczona adnotacją [jsonFilterProduit];
- entyteta [Categorie] została oznaczona adnotacją [jsonFilterCategorie];
Należy zatem zdefiniować filtry o tych nazwach.
- wiersze [34-41]: definiują filtr o nazwie [jsonMapperShortCategorie], który pozwala uzyskać reprezentację jSON kategorii bez jej produktów;
- wiersze [43-51]: definiują filtr o nazwie [jsonMapperLongCategorie], który pozwala uzyskać reprezentację jSON kategorii wraz z jej produktami;
- wiersze [53-60]: definiują filtr o nazwie [jsonMapperShortProduit], który pozwala uzyskać reprezentację jSON produktu bez jego kategorii;
- wiersze [62-70]: definiują filtr o nazwie [jsonMapperLongProduit], który pozwala uzyskać reprezentację jSON produktu wraz z kategorią;
17.3.2.2. Klasa [AppConfig]
Klasa [AppConfig] konfiguruje całą aplikację, tj. warstwy [web] i [DAO]:
package spring.webjson.server.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration
@ComponentScan(basePackages = { "spring.webjson.server.service" })
@Import({ spring.jdbc.config.AppConfig.class, WebConfig.class })
public class AppConfig {
}
- wiersz 7: klasa ta jest klasą konfiguracyjną Spring;
- wiersz 9: importujemy bean’y z warstwy [DAO / JDBC] oraz te zdefiniowane przez klasę [WebConfig]. Wszystkie bean z warstwy [DAO] będą zatem dostępne w aplikacji internetowej / jSON;
- wiersz 8: wskazuje, w których pakietach znajdują się inne komponenty Spring;
17.3.3. Wyjątek [ServerException]
![]() |
Podobnie jak w poprzednich rozdziałach, warstwa [DAO] generowała niekontrolowany wyjątek [DaoException], natomiast warstwa [web] wygeneruje niekontrolowany wyjątek [ServerException]:
package spring.webjson.server.infrastructure;
import generic.jdbc.infrastructure.UncheckedException;
public class ServerException extends UncheckedException {
private static final long serialVersionUID = 1L;
// producenci
public ServerException() {
super();
}
public ServerException(int code, Throwable e, String simpleClassName) {
super(code, e, simpleClassName);
}
}
- wiersz 5: klasa [ServerException] rozszerza klasę [UncheckedException] zdefiniowaną w projekcie konfigurującym warstwę JDBC (wiersz 3);
17.3.4. Kontrolery
![]() |
![]() |
Będziemy mieli tutaj dwa kontrolery:
- [CategorieController] będzie obsługiwać zapytania dotyczące kategorii;
- [CategorieController] będzie obsługiwać zapytania dotyczące produktów;
Metody URL udostępniane przez kontrolery odpowiadają jeden do jednego metodom interfejsów [DaoCategorie] i [DaoProduit] warstwy [DAO]:
![]() | ![]() |
Tak więc, jak pokazano powyżej:
- metoda internetowa [deleteAllCategories] wywoła metodę [deleteAllEntities] z klasy [DaoCategorie];
- metoda internetowa [getShortCategoriesById] wywoła metodę [getShortEntitiesById] z klasy [DaoCategorie];
To samo dotyczy produktów:
![]() | ![]() |
17.3.4.1. Produkty URL udostępniane przez kontroler [CategorieController]
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.4.2. URL udostępnione przez kontroler [ProduitController]
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.5. Ogólna implementacja usługi internetowej
![]() |
Lista elementów URL udostępnianych przez usługę internetową pokazuje, że oferowane są te same typy elementów URL do zarządzania kategoriami i produktami. Zamiast pisać dwa bardzo podobne kontrolery, sprawimy, by wywodziły się one z klasy, która wykona całą pracę wspólną dla obu kontrolerów. Będzie to powyższa klasa [AbstractController]. Klasa ta zaimplementuje następujący interfejs [Iws]:
package spring.webjson.server.service;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import spring.jdbc.entities.AbstractCoreEntity;
public interface Iws<T extends AbstractCoreEntity> {
// lista wszystkich elementów T
public Response<List<T>> getAllShortEntities();
public Response<List<T>> getAllLongEntities();
// poszczególnych jednostek – wersja skrócona
public Response<List<T>> getShortEntitiesById(HttpServletRequest request);
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);
// poszczególnych jednostek – wersja długa
public Response<List<T>> getLongEntitiesById(HttpServletRequest request);
public Response<List<T>> getLongEntitiesByName(HttpServletRequest request);
// aktualizacja wielu jednostek
public Response<List<T>> saveEntities(HttpServletRequest request);
// usunięcie wszystkich elementów
public Response<Void> deleteAllEntities();
// usunięcie wielu elementów
public Response<Void> deleteEntitiesById(HttpServletRequest request);
public Response<Void> deleteEntitiesByName(HttpServletRequest request);
}
Interfejs ten przejmuje metody z interfejsu warstwy [DAO], z której będziemy korzystać:
package spring.jdbc.dao;
import java.util.List;
import spring.jdbc.entities.AbstractCoreEntity;
public interface IDao<T extends AbstractCoreEntity> {
// lista wszystkich elementów T
public List<T> getAllShortEntities();
public List<T> getAllLongEntities();
// poszczególnych elementów – wersja skrócona
public List<T> getShortEntitiesById(Iterable<Long> ids);
public List<T> getShortEntitiesById(Long... ids);
public List<T> getShortEntitiesByName(Iterable<String> names);
public List<T> getShortEntitiesByName(String... names);
// określonych elementów – wersja długa
public List<T> getLongEntitiesById(Iterable<Long> ids);
public List<T> getLongEntitiesById(Long... ids);
public List<T> getLongEntitiesByName(Iterable<String> names);
public List<T> getLongEntitiesByName(String... names);
// aktualizacja wielu elementów
public List<T> saveEntities(Iterable<T> entities);
public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);
// usunięcie wszystkich elementów
public void deleteAllEntities();
// usunięcie kilku elementów
public void deleteEntitiesById(Iterable<Long> ids);
public void deleteEntitiesById(Long... ids);
public void deleteEntitiesByName(Iterable<String> names);
public void deleteEntitiesByName(String... names);
public void deleteEntitiesByEntity(Iterable<T> entities);
public void deleteEntitiesByEntity(@SuppressWarnings("unchecked") T... entities);
}
Przejście z interfejsu [IDao<T>] warstwy [DAO] do interfejsu [Iws<T>] serwisu internetowego odbyło się zgodnie z następującymi zasadami:
- metody interfejsu [Iws<T>] nie będą generować wyjątków. Jeśli wystąpi wyjątek, zostanie on zamknięty w obiekcie [Response];
- warianty parametrów, takie jak w wierszach 45 i 47 [Iterable<String> names, String... names], znikają. Metody pobierają swoje parametry z żądania HTTP od klienta typu [HttpServletRequest request];
Wszystkie odpowiedzi serwisu internetowego zostaną zawarte w następującym obiekcie [Response]:
![]() |
package spring.webjson.server.service;
public class Response<T> {
// ----------------- właściwości
// status operacji
private int status;
// komunikat o błędzie
private String exception;
// treść odpowiedzi
private T body;
// konstruktory
public Response() {
}
public Response(int status, String exception, T body) {
this.status = status;
this.exception = exception;
this.body = body;
}
// metody pobierające i ustawiające
...
}
- wiersz 4: odpowiedź zawiera typ T;
- wiersz 12: odpowiedź typu T;
- wiersze 7–10: może się zdarzyć, że metoda napotka wyjątek. W takim przypadku zwróci odpowiedź zawierającą:
- wiersz 8: status!=0;
- wiersz 10: komunikat o błędzie;
Klasa [AbstractController] ma następującą postać:
package spring.webjson.server.service;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.AbstractCoreEntity;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.infrastructure.ServerException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
@Autowired
protected ApplicationContext context;
// warstwa DAO
private IDao<T> dao;
abstract protected IDao<T> getDao();
// lokalny
private String simpleClassName = getClass().getSimpleName();
@PostConstruct
public void init(){
dao=getDao();
}
@Override
public Response<List<T>> getAllShortEntities() {
try {
// odpowiedź
return new Response<List<T>>(0, null, dao.getAllShortEntities());
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1007, e, simpleClassName).toString(), null);
}
}
@Override
public Response<List<T>> getAllLongEntities() {
try {
// odpowiedź
return new Response<List<T>>(0, null, dao.getAllLongEntities());
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1008, e, simpleClassName).toString(), null);
}
}
@Override
public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> getLongEntitiesById(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> getLongEntitiesByName(HttpServletRequest request) {
...
}
@Override
public Response<List<T>> saveEntities(HttpServletRequest request) {
return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
}
@Override
public Response<Void> deleteAllEntities() {
try {
// usuwamy
dao.deleteAllEntities();
// odpowiedź
return new Response<Void>(0, null, null);
} catch (DaoException e) {
return new Response<Void>(1, e.toString(), null);
} catch (Exception e) {
return new Response<Void>(2, new ServerException(1014, e, simpleClassName).toString(), null);
}
}
@Override
public Response<Void> deleteEntitiesById(HttpServletRequest request) {
...
}
@Override
public Response<Void> deleteEntitiesByName(HttpServletRequest request) {
...
}
}
Wszystkie metody są zaimplementowane w ten sam sposób:
- jeśli oczekują informacji, pobierają je z obiektu [HttpServletRequest request];
- wywołują metodę warstwy [DAO] o tej samej nazwie co one same;
- obsługują wyjątki, które mogą wystąpić albo w operacji 1 pobierania parametrów, albo w operacji 2 wywołania warstwy [DAO];
Przyjrzyjmy się najpierw, w jaki sposób warstwa [DAO] jest wstrzykiwana do klasy [AbstractController]:
public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
@Autowired
protected ApplicationContext context;
// warstwa DAO
private IDao<T> dao;
abstract protected IDao<T> getDao();
@PostConstruct
public void init(){
dao=getDao();
}
- wiersz 1: klasa jest abstrakcyjna i implementuje interfejs generyczny [Iws<T>];
- wiersze 3–4: wstrzyknięcie kontekstu Spring;
- wiersz 7: wciąż nieznane odwołanie do warstwy [DAO], która ma zostać użyta;
- wiersz 9: abstrakcyjna metoda [getDao], która zwróci odwołanie do warstwy [DAO], która ma zostać użyta. Metoda ta zostanie przedefiniowana przez klasę potomną, a zatem to właśnie klasa potomna wskaże, która warstwa [DAO] ma zostać użyta (DaoProduit lub DaoCategorie);
- wiersz 11: adnotacja [@PostConstruct] oznacza metodę, która ma zostać wykonana po zakończeniu instancjonowania obiektu. Po zakończeniu instancjonowania wstrzyknięcia Spring zostały już wykonane. Klasa potomna uzyska wówczas odwołanie do swojej warstwy [DAO] i będzie mogła je przekazać klasie nadrzędnej;
Metoda [getShortEntitiesById] ma następującą postać:
@Override
public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
try {
// odzyskuje się wartość przesłaną
String body = CharStreams.toString(request.getReader());
// deserializujemy ją
ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
List<Long> ids = mapper.readValue(body, new TypeReference<List<Long>>() {
});
// odpowiedź
return new Response<List<T>>(0, null, dao.getShortEntitiesById(ids));
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1009, e, simpleClassName).toString(), null);
}
}
- wiersz 5: wartość przesłana przez klienta będzie ciągiem znaków jSON. W tym miejscu jest ona pobierana;
- wiersze 7–9: ciąg znaków jSON zawiera listę kluczy głównych podmiotów, dla których potrzebna jest skrócona wersja;
- wiersz 11: wywoływana jest metoda o tej samej nazwie [DAO]. Odpowiedź typu [List<T>] jest zawarta w obiekcie [Response];
- wiersz 13: przypadek, w którym warstwa [DAO] zgłosiła wyjątek;
- wiersz 15: przypadek pozostałych wyjątków, w szczególności możliwego wyjątku podczas deseryalizacji parametru jSON, wiersz 8;
Metoda [getShortEntitiesByName] działa analogicznie:
@Override
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
try {
// odbieramy przesłaną wartość
String body = CharStreams.toString(request.getReader());
// deserializujemy ją
ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
List<String> noms = mapper.readValue(body, new TypeReference<List<String>>() {
});
// odpowiedź
return new Response<List<T>>(0, null, dao.getShortEntitiesByName(noms));
} catch (DaoException e) {
return new Response<List<T>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<T>>(2, new ServerException(1010, e, simpleClassName).toString(), null);
}
}
- wiersze 4–9: w tym przypadku parametr jSON stanowi listę nazw kategorii, dla których wymagana jest wersja skrócona;
Metoda [saveEntities] nie została zaimplementowana, ponieważ jest w znacznym stopniu zależna od charakteru entytety, którą należy zapisać – [Categorie] lub [Produit]. Kod do wyodrębnienia jest niewielki. Zadanie to pozostawia się zatem klasom potomnym.
@Override
public Response<List<T>> saveEntities(HttpServletRequest request) {
return new Response<List<T>>(2, new ServerException(1013, new RuntimeException("[saveEntities] not implemented"), simpleClassName).toString(), null);
}
17.3.6. Kontroler [CategorieController]
![]() |
Kontroler [CategorieController] zarządza przetwarzaniem URL w odniesieniu do kategorii:
package spring.webjson.server.service;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Categorie;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreCategorie;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
@RestController
public class CategorieController extends AbstractController<Categorie> {
@Autowired
private IDao<Categorie> daoCategorie;
@Override
protected IDao<Categorie> getDao() {
return daoCategorie;
}
// lokalny
private String simpleClassName = getClass().getSimpleName();
@RequestMapping(value = "/getAllShortCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllShortCategories() {
// nadrzędny
Response<List<Categorie>> response = super.getAllShortEntities();
// filtry serializacji jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/getAllLongCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllLongCategories() {
// nadrzędny
Response<List<Categorie>> response = super.getAllLongEntities();
// filtry serializacji jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/getShortCategoriesById", method = RequestMethod.POST)
public Response<List<Categorie>> getShortCategoriesById(HttpServletRequest request) {
// element nadrzędny
Response<List<Categorie>> response = super.getShortEntitiesById(request);
// filtry serializacji jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/getShortCategoriesByName", method = RequestMethod.POST)
public Response<List<Categorie>> getShortCategoriesByName(HttpServletRequest request) {
// element nadrzędny
Response<List<Categorie>> response = super.getShortEntitiesByName(request);
// filtry serializacji jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/getLongCategoriesById", method = RequestMethod.POST)
public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request) {
// element nadrzędny
Response<List<Categorie>> response = super.getLongEntitiesById(request);
// filtry serializacji jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/getLongCategoriesByName", method = RequestMethod.POST)
public Response<List<Categorie>> getLongCategoriesByName(HttpServletRequest request) {
// element nadrzędny
Response<List<Categorie>> response = super.getLongEntitiesByName(request);
// filtry serializacji jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
...
}
@RequestMapping(value = "/deleteAllCategories", method = RequestMethod.GET)
public Response<Void> deleteAllCategories() {
return super.deleteAllEntities();
}
@RequestMapping(value = "/deleteCategoriesById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteCategoriesById(HttpServletRequest request) {
return super.deleteEntitiesById(request);
}
@RequestMapping(value = "/deleteCategoriesByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteCategoriesByName(HttpServletRequest request) {
return super.deleteEntitiesByName(request);
}
}
- wiersz 26: klasa [CategorieController] rozszerza klasę [AbstractController];
- wiersz 25: adnotacja [@RestController] sprawia, że klasa staje się komponentem Spring. Adnotacja ta wskazuje ponadto, że klasa jest usługą internetową, której metody wysyłają odpowiedzi bezpośrednio do klienta w formacie jSON;
- wiersze 28–29: w tym miejscu wstrzykiwane jest odwołanie do warstwy [DAO];
- wiersze 31–34: redefinicja metody [getDao], zadeklarowanej jako abstrakcyjna w klasie nadrzędnej, której celem jest zwrócenie odwołania do warstwy [DAO] do wykorzystania;
Wszystkie metody są zbudowane według tego samego wzorca:
- przekazanie przetwarzania do klasy nadrzędnej;
- inicjalizacja mapera jSON, który przeprowadzi serializację odpowiedzi;
- wysłanie odpowiedzi;
Przyjrzyjmy się sygnaturze kilku metod URL:
| - funkcja URL [/getAllShortCategories] jest wywoływana z parametrem GET |
| - funkcja URL [/getShortCategoriesById] jest wywoływana z funkcją POST. Wartością przekazaną jest ciąg znaków jSON zawierający klucze główne pożądanych kategorii; |
| – funkcja URL [/getLongCategoriesByName] jest wywoływana z POST. Wartością wysyłaną jest ciąg znaków jSON zawierający nazwy pożądanych kategorii; |
Teraz przyjrzyjmy się bliżej metodzie [saveCategories], która nie jest zgodna z formatem pozostałych metod:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
// zapisujemy kategorie
try {
// odzyskuje się wartość przesłaną
String body = CharStreams.toString(request.getReader());
// deserializujemy ją
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
// zapisujemy kategorie
categories = daoCategorie.saveEntities(categories);
// zwracamy wynik
List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
for (Categorie categorie : categories) {
CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
coreCategories.add(coreCategorie);
List<Produit> produits = categorie.getProduits();
if (produits != null) {
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : categorie.getProduits()) {
coreProduits.add(new CoreProduit(produit.getId()));
}
coreCategorie.setCoreProduits(coreProduits);
}
}
// wynik
return new Response<List<CoreCategorie>>(0, null, coreCategories);
} catch (DaoException e) {
return new Response<List<CoreCategorie>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<CoreCategorie>>(2, new ServerException(1020, e, simpleClassName).toString(), null);
}
}
- wiersz 1: ciągowi URL [/saveCategories] towarzyszy wartość wysyłana. Jest to ciąg jSON zawierający pełne nazwy kategorii, które mają zostać zachowane;
- wiersze 5–10: kategorie, które mają zostać zachowane, są odtwarzane na podstawie ciągu znaków jSON. Łącze [produit.categorie], które łączy [Produit] z jego [Catégorie], ma wartość null, ponieważ w długiej wersji [Categorie] każdy element [Produit] występuje w wersji skróconej, bez pola [categorie]. Nie stanowi to przeszkody, ponieważ warstwa [DAO] zaimplementowana przy użyciu JDBC nie potrzebuje tej informacji;
- wiersz 12: kategorie są zapisywane w bazie. Otrzymana lista kategorii została wzbogacona o klucze główne zapisanych elementów, kategorii i produktów. Nic więcej się nie zmieniło. Zamiast zwracać całą otrzymaną listę, co wiąże się z pewnym obciążeniem, zwracamy jedynie klucze główne elementów tej listy. W tym celu wykorzystujemy następujące klasy: [CoreCategorie] i [CoreProduit]:
![]() |
package spring.webjson.server.entities;
import java.util.List;
public class CoreCategorie {
// klucz główny
private Long id;
// producenci
public CoreCategorie() {
}
public CoreCategorie(Long id) {
this.id=id;
}
// lista produktów
private List<CoreProduit> coreProduits;
// metody pobierające i ustawiające
...
}
- wiersz 8: klucz podstawowy produktu;
- wiersz 20: klucze główne tych produktów;
package spring.webjson.server.entities;
public class CoreProduit {
// klucz główny
private Long id;
// konstruktory
public CoreProduit() {
}
public CoreProduit(Long id) {
this.id = id;
}
// metody pobierające i ustawiające
...
}
- wiersz 6: klucz podstawowy produktu;
Wróćmy do kodu metody [saveCategories]:
...
// zapisujemy kategorie
categories = daoCategorie.saveEntities(categories);
// zwracamy wynik
List<CoreCategorie> coreCategories = new ArrayList<CoreCategorie>();
for (Categorie categorie : categories) {
CoreCategorie coreCategorie = new CoreCategorie(categorie.getId());
coreCategories.add(coreCategorie);
List<Produit> produits = categorie.getProduits();
if (produits != null) {
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : categorie.getProduits()) {
coreProduits.add(new CoreProduit(produit.getId()));
}
coreCategorie.setCoreProduits(coreProduits);
}
}
// wynik
return new Response<List<CoreCategorie>>(0, null, coreCategories);
...
- wiersze 5–17: tworzymy listę [CoreCategorie], którą zwrócimy klientowi zdalnemu;
- wiersz 19: odpowiedź jest zwracana i serializowana w formacie jSON;
17.3.7. Zarządzanie filtrami jSON
Dla każdej metody kontrolera istnieją dwa momenty serializacji / deserializacji jSON:
- deserializacja wartości wprowadzonej: jest tu obsługiwana jawnie;
- serializacja wyniku: jest tutaj obsługiwana niejawnie;
Zacznijmy od deseryalizacji wartości przesłanej w [CategorieController.saveCategories]:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
// zapisujemy kategorie
try {
// pobieranie wartości przesłanej
String body = CharStreams.toString(request.getReader());
// deserializujemy ją
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
- wiersz 8: w kontekście Springa pobieramy mapper skonfigurowany do obsługi filtra jSON [jsonMapperLongCategorie]. Wróćmy do definicji tego mappera w klasie konfiguracyjnej [WebConfig]:
// -------------------------------- konfiguracja filtrów [json]
// mapowanie jSON
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
final ObjectMapper objectMapper = new ObjectMapper();
converter.setObjectMapper(objectMapper);
return converter;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(mappingJackson2HttpMessageConverter());
super.configureMessageConverters(converters);
}
// filtry jSON
@Bean
public ObjectMapper jsonMapper(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
return mappingJackson2HttpMessageConverter.getObjectMapper();
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperShortCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
return jsonMapper;
}
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
ObjectMapper jsonMapperLongCategorie(MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter) {
ObjectMapper jsonMapper = jsonMapper(mappingJackson2HttpMessageConverter);
jsonMapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept()).addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
return jsonMapper;
}
- wiersze 32–40: mapper jSON [jsonMapperLongCategorie] pobrany przez klasę [CategorieController];
- wiersz 35: ten mapper jest zwracany przez metodę [jsonMapper] z wierszy 18–21;
- wiersze 18–21: metoda [jsonMapper] zwraca mapper jSON z konwertera [MappingJackson2HttpMessageConverter] z wierszy 3–9;
Innymi słowy, mapper jSON pobierany przez wiersz 4 poniżej z [CategorieController.saveCategories]:
// odbieramy przesłaną wartość
String body = CharStreams.toString(request.getReader());
// deserializujemy ją
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
jest konwerterem używanym domyślnie przez Spring MVC do deseryalizacji wartości przesłanej przez klienta oraz serializacji wyniku, który jest do niego wysyłany. W powyższych wierszach nie nastąpiła domyślna deseryalizacja przesłanej wartości. Aby to zrobić, należałoby napisać:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(@RequestBody List<Categorie> categories) {
W tym przypadku nastąpiłaby automatyczna deseryalizacja wartości przesłanej w parametrze [categories]. Pojawiłby się jednak problem z filtrem [jsonFilterCategorie], który posiadają encje [Categorie]. Należy go skonfigurować. Z tego powodu zdecydowaliśmy się na jawną deseryalizację (wiersze 4–5). Drugą kwestią, na którą należy zwrócić uwagę, jest to, że mapper z wiersza 4 (który jest domyślnie używany przez Spring MVC) nadaje się również do serializacji wyniku [Response<List<CoreCategorie>]. W rzeczywistości encja [CoreCategorie] nie posiada filtra jSON. Nie ma zatem potrzeby konfigurowania mapera jSON uzyskany z dodatkowym filtrem. W tym przypadku nastąpi domyślna serializacja odpowiedzi wysyłanej do klienta.
17.3.8. Kontroler [ProduitController]
![]() |
Kontroler [ProduitController] zarządza przetwarzaniem URL dotyczących produktów. Jego kod jest analogiczny do kodu kontrolera [CategorieController]:
package spring.webjson.server.service;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import spring.jdbc.dao.IDao;
import spring.jdbc.entities.Produit;
import spring.jdbc.infrastructure.DaoException;
import spring.webjson.server.entities.CoreProduit;
import spring.webjson.server.infrastructure.ServerException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
@RestController
public class ProduitController extends AbstractController<Produit> {
@Autowired
private IDao<Produit> daoProduit;
@Override
protected IDao<Produit> getDao() {
return daoProduit;
}
// lokalny
private String simpleClassName = getClass().getSimpleName();
@RequestMapping(value = "/getAllShortProduits", method = RequestMethod.GET)
public Response<List<Produit>> getAllShortProduits() {
// element nadrzędny
Response<List<Produit>> response = super.getAllShortEntities();
// filtry serializacji jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/getAllLongProduits", method = RequestMethod.GET)
public Response<List<Produit>> getAllLongProduits() {
// nadrzędny
Response<List<Produit>> response = super.getAllLongEntities();
// filtry serializacji jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/getShortProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getShortProduitsById(HttpServletRequest request) {
// element nadrzędny
Response<List<Produit>> response = super.getShortEntitiesById(request);
// filtry serializacji jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/getShortProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getShortProduitsByName(HttpServletRequest request) {
// element nadrzędny
Response<List<Produit>> response = super.getShortEntitiesByName(request);
// filtry serializacji jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/getLongProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getLongProduitsById(HttpServletRequest request) {
// element nadrzędny
Response<List<Produit>> response = super.getLongEntitiesById(request);
// filtry serializacji jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/getLongProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getLongProduitsByName(HttpServletRequest request) {
// element nadrzędny
Response<List<Produit>> response = super.getLongEntitiesByName(request);
// filtry serializacji jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// odpowiedź
return response;
}
@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
...
}
@RequestMapping(value = "/deleteAllProduits", method = RequestMethod.GET)
public Response<Void> deleteAllProduits() {
return super.deleteAllEntities();
}
@RequestMapping(value = "/deleteProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteProduitsById(HttpServletRequest request) {
return super.deleteEntitiesById(request);
}
@RequestMapping(value = "/deleteProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<Void> deleteProduitsByName(HttpServletRequest request) {
return super.deleteEntitiesByName(request);
}
}
Jedynie metoda [saveProduits] ma strukturę odmienną od pozostałych metod:
@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
try {
// odbieramy przesłaną wartość
String body = CharStreams.toString(request.getReader());
// deserializujemy ją
ObjectMapper mapper = context.getBean("jsonMapperShortProduit", ObjectMapper.class);
List<Produit> produits = mapper.readValue(body, new TypeReference<List<Produit>>() {
});
// zapisujemy produkty
produits = daoProduit.saveEntities(produits);
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : produits) {
coreProduits.add(new CoreProduit(produit.getId()));
}
// zwracamy odpowiedź
return new Response<List<CoreProduit>>(0, null, coreProduits);
} catch (DaoException e) {
return new Response<List<CoreProduit>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<CoreProduit>>(2, new ServerException(1021, e, simpleClassName).toString(), null);
}
}
- wiersze 4–9: na podstawie otrzymanego ciągu znaków jSON odtwarza się listę [Produit], które mają zostać zachowane. Ponieważ otrzymany ciąg jSON dotyczy skróconych wersji produktów, pole [categorie] tych produktów ma wartość null. Ponownie warstwa DAO / JDBC nie potrzebuje tej informacji;
- wiersz 11: produkty są zapisywane;
- wiersze 12–15: tworzona jest lista [CoreProduit] do zwrócenia;
- wiersz 18: zwracana jest odpowiedź, która zostanie zserializowana (domyślna serializacja przeprowadzana przez Spring MVC) przez mapper z wiersza 7 przed wysłaniem do klienta zdalnego (por. omówienie w paragrafie 17.3.7);
17.3.9. Klasa wykonawcza serwisu internetowego / jSON
![]() |
Klasa [Boot] jest klasą wykonalną projektu:
package spring.webjson.server.boot;
import org.springframework.boot.SpringApplication;
import spring.webjson.server.config.AppConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
}
- wiersz 10: wykonywana jest metoda statyczna [SpringApplication.run]. Klasa [SpringApplication] jest klasą projektu [spring Boot] (wiersz 3). Przekazywane są do niej dwa parametry:
- [AppConfig.class]: klasa, która konfiguruje całą aplikację;
- [args]: ewentualne argumenty przekazane do metody [main] w wierszu 9. Ten parametr nie jest tutaj wykorzystywany;
Po uruchomieniu tej klasy pojawiają się następujące logi:
- wiersze 11–15: wykryto komponenty definiujące filtry jSON. Przejmują one kontrolę nad komponentami o tych samych nazwach wykrytymi w projekcie konfiguracyjnym warstwy JDBC;
- wiersze 17–18: uruchomienie serwera Tomcat, który będzie obsługiwał serwis internetowy / jSON;
- wiersze 19–21: inicjowany jest kontekst Springa MVC;
- wiersze 24–43: wykryto udostępnione URL;
17.3.10. Testy serwisu internetowego / jSON
Aby przeprowadzić testy, używamy klienta [Advanced Rest Client] (patrz paragraf 23.11) do wysyłania zapytań do URL udostępnianych przez serwis internetowy / jSON (usługa internetowa / jSON musi być oczywiście uruchomiona, podobnie jak SGBD). Aby zapełnić bazę danych, uruchamiamy konfigurację wykonawczą o nazwie [spring-jdbc-generic-04-fillDataBase], która wypełnia bazę danymi zawierającymi 5 kategorii i 10 produktów:
![]() |
![]() |
- w [1-3] żądamy URL i [/getAllLongCategories] za pomocą polecenia HTTP i GET;
Otrzymujemy następującą odpowiedź:
![]() |
- w [1], żądanie klienta HTTP;
- w [2], odpowiedź serwera HTTP;
- w [3] status [200 OK] wskazuje, że serwer poprawnie przetworzył żądanie;
- w [4] – odpowiedź serwera jSON;
Pełna odpowiedź jSON wygląda następująco:
{"status":0,"exception":null,"body":[{"id":1880,"version":1,"nom":"categorie[0]","produits":[{"id":9072,"version":1,"nom":"produit[0,0]","idCategorie":1880,"prix":100.0,"description":"desc[0,0]"},{"id":9073,"version":1,"nom":"produit[0,1]","idCategorie":1880,"prix":101.0,"description":"desc[0,1]"},{"id":9074,"version":1,"nom":"produit[0,2]","idCategorie":1880,"prix":102.0,"description":"desc[0,2]"},{"id":9075,"version":1,"nom":"produit[0,3]","idCategorie":1880,"prix":103.0,"description":"desc[0,3]"},{"id":9076,"version":1,"nom":"produit[0,4]","idCategorie":1880,"prix":104.0,"description":"desc[0,4]"}]},{"id":1881,"version":1,"nom":"categorie[1]","produits":[{"id":9077,"version":1,"nom":"produit[1,0]","idCategorie":1881,"prix":110.00000000000001,"description":"desc[1,0]"},{"id":9078,"version":1,"nom":"produit[1,1]","idCategorie":1881,"prix":111.00000000000001,"description":"desc[1,1]"},{"id":9079,"version":1,"nom":"produit[1,2]","idCategorie":1881,"prix":112.00000000000001,"description":"desc[1,2]"},{"id":9080,"version":1,"nom":"produit[1,3]","idCategorie":1881,"prix":112.99999999999999,"description":"desc[1,3]"},{"id":9081,"version":1,"nom":"produit[1,4]","idCategorie":1881,"prix":114.00000000000001,"description":"desc[1,4]"}]}]}
- status:0 oznacza, że po stronie serwera nie wystąpiły żadne błędy;
- wyjątek: null oznacza, że nie ma komunikatu o błędzie;
- treść: to treść odpowiedzi, w tym przypadku lista kategorii wraz z produktami. Są dwie kategorie, a każda z nich zawiera 5 produktów;
Dodamy do kategorii [categorie1] produkt [produit15]. W tym celu wykorzystamy produkt URL oraz [/saveProduits], które oczekują na ciąg jSON zawierający produkty do zapisania (dodanie / modyfikacja). Ciąg ten będzie wyglądał następująco:
[{"id":null,"version":null,"nom":"produit15","idCategorie":1881,"prix":111.0,"description":"desc15"}]}]
Żądanie do serwisu internetowego / jSON jest formułowane w następujący sposób:
![]() |
- w przypadku [1], żądana jest operacja URL;
- w [2] jest ona żądana poprzez operację POST;
- w [3] wysłano ciąg jSON;
- w [4] informuje się serwer, że zostanie mu wysłane jSON;
Odpowiedź serwera jest następująca:
![]() |
- w [1] otrzymaliśmy listę [CoreProduit] wraz z ich kluczami głównymi. W tym przypadku otrzymaliśmy listę zawierającą jeden element z kluczem głównym produktu, który właśnie wprowadziliśmy do bazy;
Teraz zapytajmy o rozszerzoną wersję kategorii o nazwie [categorie[1]:
![]() |
- w [1], zamiast żądanego URL;
- z [2] tworzymy POST;
- w [3,4] wartością przesłaną jest ciąg znaków jSON. Reprezentuje on listę nazw kategorii, dla których chcemy uzyskać wersję rozszerzoną;
Otrzymujemy następujący wynik:
![]() |
- w [5] kategoria [categorie[1]] ma teraz szósty produkt;
Usuńmy teraz ten produkt:
![]() |
- w [1], żądany URL;
- z [2] tworzymy POST;
- z [3-4] wysyła się ciąg jSON zawierający listę kluczy głównych produktów, które chcemy usunąć;
Otrzymany wynik jest następujący:
![]() |
- [status:0] wskazuje, że usunięcie przebiegło pomyślnie;
Teraz sprawdźmy produkt [produit[1,5]], aby upewnić się, że został on rzeczywiście usunięty:
![]() |
Otrzymujemy następujący wynik:
![]() |
- [status:0] wskazuje, że operacja przebiegła bez wyjątków;
- [body:[0]] wskazuje, że [body] jest listą zawierającą 0 elementów. Entyteta [produit[1,5]] została zatem pomyślnie usunięta;
Wszystkie operacje [GET] można wykonać w zwykłej przeglądarce:
![]() |
Zachęcamy czytelnika do przetestowania pozostałych operacji URL serwisu internetowego / jSON.








































