17. Розміщення бази даних у мережі Інтернет
17.1. Архітектура веб-сервісу / jSON
Ми реалізуємо таку архітектуру:
![]() |
- у [1] шари [DAO, [JPA], JDBC] реалізовані за допомогою однієї з 24 конфігурацій, представлених у попередніх розділах, зокрема в параграфі 15;
- шар [DAO] [3] віддаленого клієнта реалізує той самий інтерфейс, що й шар [DAO] [1], що дозволяє нам використовувати той самий тестовий шар, що й у попередніх розділах. Все відбувається так, ніби шари [2-3] є прозорими для шару [4];
Ми будемо спиратися на такі проекти:
- проект [sgbd-config-jdbc], який налаштовує шар JDBC одного з шести SGBD;
- проект [sgbd-config-jpa-*], який налаштовує шар JPA для SGBD, обраного для однієї з трьох досліджуваних реалізацій JPA (Hibernate, EclipseLink, OpenJpa);
- універсальний проєкт [spring-jdbc-04], який реалізує рівень [DAO] [1];
- загальний проєкт [spring-jpa-generic], який реалізує шари [DAO] та [2];
- загальний проект [spring-webjson-server-jdbc-generic], який реалізує веб-сервіс на основі проекту [spring-jdbc-04];
- загальний проект [spring-webjson-server-jpa-generic], який реалізує веб-сервіс на основі проекту [spring-jpa-generic];
- генеральний клієнт [spring-webjson-client-generic], який буде єдиним клієнтом для 24 конфігурацій веб-сервісу;
17.2. Налаштування робочого середовища
Ми будемо працювати з такими елементами:
- SGBD MySQL 5.6.25;
- імплементація JPA Hibernate;
Імпортуйте наступні проекти в STS:
![]() |
- проекти [spring-webjson-*] знаходяться у папці [<exemples>\spring-database-generic\spring-webjson];
- виконайте [Alt-F5], а потім перегенеруйте всі вищезазначені проекти;
Щоб перевірити правильність встановлення робочого середовища, виконайте такі дії:
- запустіть веб-сервіс із конфігурацією виконання [spring-webjson-server-jpa-generic-hibernate], яка базується на реалізації JPA / Hibernate;
![]() | ![]() |
потім:
- запустіть клієнт цього веб-сервісу з конфігурацією виконання [spring-webjson-client-generic], яка є тестом JUnit:
![]() | ![]() |
Тест має пройти успішно:
![]() |
- у [1] зупиніть веб-сервіс, а потім запустіть його з конфігурацією виконання [spring-webjson-server-jdbc-generic], яка базується на реалізації JDBC:
![]() | ![]() |
потім запустіть клієнт цього веб-сервісу з конфігурацією виконання [spring-webjson-client-generic]:
![]() | ![]() |
Тест має пройти успішно:
![]() |
17.3. Реалізація веб-сервісу / jSON / JDBC
Спочатку розглянемо таку архітектуру:
![]() |
де рівень [DAO] [1] безпосередньо взаємодіє з рівнем JDBC рівня SGBD.
17.3.1. Проєкт Eclipse веб-сервісу
Проект Eclipse веб-сервісу / jSON / JDBC має такий вигляд:
![]() |
Це проект Maven, файл якого [pom.xml] виглядає наступним чином:
<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>
<!-- веб-шар -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- шар [DAO] -->
<dependency>
<groupId>dvp.spring.database</groupId>
<artifactId>spring-jdbc-generic-04</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<!-- плагіни -->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- рядки 11–15: батьківський проект Maven;
- рядки 24–28: залежність від шару [DAO / JDBC], реалізованого проектом [spring-jdbc-generic-04];
- рядки 19–22: залежність від артефакту [spring-boot-starter-web]. Цей артефакт містить усі залежності, необхідні для створення веб-сервісу / jSON. Він також містить непотрібні бібліотеки. Тому потрібна більш точна конфігурація, але ця конфігурація зручна для початку роботи.
Залежності, що надаються цією конфігурацією, такі:
![]() | ![]() | 1 ![]() |
- у [1] видно, що Eclipse виявив залежність від архіву проєкту [spring-jdbc-generic-04];
Вищезазначені залежності є спільними як для шару [DAO], так і для шару [web].
17.3.2. Налаштування шару [web]
Рівень [web] налаштовується за допомогою двох конфігураційних файлів Spring:
![]() |
17.3.2.1. Клас [WebConfig]
Основна роль класу [WebConfig] полягає у налаштуванні:
- сервер Tomcat, на якому буде розгорнуто веб-сервіс;
- фільтри jSON для серіалізації/десеріалізації об’єктів [Produit] та [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 {
// -------------------------------- конфігурація рівня [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);
}
// -------------------------------- конфігурація фільтрів [json]
...
}
- рядок 25: клас є класом конфігурації Spring;
- рядок 26: анотація [@EnableWebMvc] вказує, що веб-шар реалізовано за допомогою Spring MVC. Це призведе до неявних налаштувань, які нам не доведеться виконувати;
- рядки 30–31: ін'єкція контексту Spring для додатка;
- рядки 33–37: визначення біна [dispatcherServlet], який у додатках Spring MVC виконує роль [FrontController], завданням якого є перенаправлення запитів клієнтів до контролера, здатного їх обробити;
- рядки 39–42: реєструється сервлет веб-сервісу, а також URL, які він обробляє. Тут вказано [/*], що означає усі URLs;
- рядки 44–47: визначення біна [embeddedServletContainerFactory], який визначає веб-сервер, що буде використовуватися. У цьому випадку це буде веб-сервер Tomcat [http://tomcat.apache.org/]. Також можна використовувати сервер Jetty [http://www.eclipse.org/jetty/]. Обидва ці сервери вбудовані в залежності Maven. Коли [Spring Boot] керує запуском проєкту, він автоматично запускає веб-сервер, вказаний у конфігурації, і розгортає на ньому службу або веб-додаток;
Налаштування фільтрів jSON здійснюється наступним чином:
package spring.webjson.server.config;
import java.util.List;
...
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- конфігурація рівня [web]
...
// -------------------------------- конфігурація фільтрів [json]
// відповідність 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);
}
// фільтри 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;
}
}
- рядок 8: клас [WebConfig] успадковує клас [WebMvcConfigurerAdapter]. Останній клас налаштовує веб-додаток із значеннями за замовчуванням. Якщо потрібно налаштувати цю конфігурацію, необхідно перевизначити певні методи цього класу. У даному випадку ми хочемо перевизначити метод [configureMessageConverters] класу [22-26] (зверніть увагу на анотацію @Override), який визначає список «конвертерів». Веб-сервіс /jSON та його клієнт обмінюються рядками тексту. Конвертер — це інструмент, здатний створити об’єкт на основі отриманого рядка тексту (десеріалізація) та створити рядок тексту на основі об’єкта (серіалізація). У даному випадку рядками тексту будуть рядки jSON. Отже, ми говоритимемо про серіалізацію/десеріалізацію jSON;
- рядок 23: метод [configureMessageConverters] отримує як параметр список конвертерів;
- рядки 24–25: конвертер jSON [MappingJackson2HttpMessageConverter] рядків [14-20] додається до цього списку. Це дозволить здійснювати обмін даними jSON між клієнтом і сервером;
- рядки [14-20]: визначають конвертер jSON, реалізований класом [MappingJackson2HttpMessageConverter]. Цей клас (рядок 10) можна знайти серед залежностей Maven проекту;
- рядки [17-18]: створюється мапер jSON, який прив’язується до конвертера [MappingJackson2HttpMessageConverter];
- рядки [29-32]: визначають маппер jSON, створений у рядку 17, як Spring-бін. Це розміщує його в контексті Spring і робить доступним для ін’єкції в інші біни або використання в коді веб-додатку;
- рядки 34–41: визначають фільтр jSON для попереднього маппера jSON;
- рядок 35: анотація [@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)] гарантує, що визначений тут bean не є синглтоном. Кожного разу, коли його запитуватимуть у контексті, метод [jsonMapperCategorieWithoutProduits] виконуватиметься заново. Це необхідно в даному випадку, оскільки ми визначаємо чотири фільтри jSON. Однак у будь-який момент часу активним має бути лише один. Надаючи біну область дії [ConfigurableBeanFactory.SCOPE_PROTOTYPE], ми гарантуємо, що метод буде виконано повторно, а попередній фільтр буде замінено новим;
- щоб зрозуміти ці фільтри, слід пам’ятати, що в шарі [DAO]:
- об’єкт [Produit] було позначено анотацією [jsonFilterProduit];
- об’єкт [Categorie] було позначено анотацією [jsonFilterCategorie];
Отже, потрібно визначити фільтри з цими іменами.
- рядки [34-41]: визначають фільтр під назвою [jsonMapperShortCategorie], який дозволяє отримати представлення jSON категорії без її товарів;
- рядки [43-51]: визначають фільтр під назвою [jsonMapperLongCategorie], який дозволяє отримати представлення jSON категорії разом з її товарами;
- рядки [53-60]: визначають фільтр під назвою [jsonMapperShortProduit], який дозволяє отримати представлення jSON товару без його категорії;
- рядки [62-70]: визначають фільтр під назвою [jsonMapperLongProduit], який дозволяє отримати представлення jSON товару разом з його категорією;
17.3.2.2. Клас [AppConfig]
Клас [AppConfig] налаштовує весь додаток, тобто шари [web] та [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 {
}
- рядок 7: клас є класом конфігурації Spring;
- рядок 9: імпортуються біни з шару [DAO / JDBC], а також ті, що визначені класом [WebConfig]. Отже, усі біни з шару [DAO] будуть доступні у веб-додатку / jSON;
- рядок 8: вказує, у яких пакетах можна знайти інші біни Spring;
17.3.3. Виняток [ServerException]
![]() |
Так само, як і в попередніх розділах, рівень [DAO] генерував неконтрольоване виключення [DaoException], рівень [web] генеруватиме неконтрольоване виключення [ServerException]:
package spring.webjson.server.infrastructure;
import generic.jdbc.infrastructure.UncheckedException;
public class ServerException extends UncheckedException {
private static final long serialVersionUID = 1L;
// виробники
public ServerException() {
super();
}
public ServerException(int code, Throwable e, String simpleClassName) {
super(code, e, simpleClassName);
}
}
- рядок 5: клас [ServerException] успадковує клас [UncheckedException], визначений у проєкті, що конфігурує рівень JDBC (рядок 3);
17.3.4. Контролери
![]() |
![]() |
Тут ми матимемо два контролери:
- [CategorieController] контролюватиме запити щодо категорій;
- [CategorieController] оброблятиме запити щодо товарів;
Методи URL, що експонуються контролерами, один до одного відповідають методам інтерфейсів [DaoCategorie] та [DaoProduit] шару [DAO]:
![]() | ![]() |
Отже, як показано вище:
- веб-метод [deleteAllCategories] викликатиме метод [deleteAllEntities] класу [DaoCategorie];
- веб-метод [getShortCategoriesById] викликатиме метод [getShortEntitiesById] класу [DaoCategorie];
Те саме стосується продуктів:
![]() | ![]() |
17.3.4.1. URL, що надаються контролером [CategorieController]
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.4.2. URL, представлені контролером [ProduitController]
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
17.3.5. Загальна реалізація веб-сервісу
![]() |
Список URL, що надається веб-сервісом, показує, що для управління категоріями та товарами пропонуються однакові типи URL. Замість того, щоб писати два дуже схожі контролери, ми зробимо їх похідними від класу, який виконуватиме всю роботу, спільну для обох контролерів. Це буде клас [AbstractController], наведений вище. Цей клас реалізує наступний інтерфейс [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> {
// перелік усіх об’єктів T
public Response<List<T>> getAllShortEntities();
public Response<List<T>> getAllLongEntities();
// окремих об’єктів — коротка версія
public Response<List<T>> getShortEntitiesById(HttpServletRequest request);
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request);
// окремих об’єктів — повна версія
public Response<List<T>> getLongEntitiesById(HttpServletRequest request);
public Response<List<T>> getLongEntitiesByName(HttpServletRequest request);
// оновлення декількох об’єктів
public Response<List<T>> saveEntities(HttpServletRequest request);
// видалення всіх об’єктів
public Response<Void> deleteAllEntities();
// видалення декількох об’єктів
public Response<Void> deleteEntitiesById(HttpServletRequest request);
public Response<Void> deleteEntitiesByName(HttpServletRequest request);
}
Цей інтерфейс переймає методи інтерфейсу рівня [DAO], який буде використовуватися:
package spring.jdbc.dao;
import java.util.List;
import spring.jdbc.entities.AbstractCoreEntity;
public interface IDao<T extends AbstractCoreEntity> {
// перелік усіх об’єктів T
public List<T> getAllShortEntities();
public List<T> getAllLongEntities();
// окремих об’єктів — коротка версія
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);
// окремих об’єктів — повна версія
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);
// оновлення декількох об’єктів
public List<T> saveEntities(Iterable<T> entities);
public List<T> saveEntities(@SuppressWarnings("unchecked") T... entities);
// видалення всіх об’єктів
public void deleteAllEntities();
// видалення декількох об’єктів
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);
}
Перехід від інтерфейсу [IDao<T>] шару [DAO] до інтерфейсу [Iws<T>] веб-сервісу відбувався за такими правилами:
- методи інтерфейсу [Iws<T>] не повинні генерувати винятків. Якщо виняток все ж виникне, він буде інкапсульований в об’єкт [Response];
- варіанти параметрів, такі як рядки 45 і 47 [Iterable<String> names, String... names], зникають. Методи отримують свої параметри із запиту HTTP від клієнта типу [HttpServletRequest request];
Усі відповіді веб-сервісу будуть інкапсульовані в наступний об’єкт [Response]:
![]() |
package spring.webjson.server.service;
public class Response<T> {
// ----------------- властивості
// статус операції
private int status;
// повідомлення про помилку
private String exception;
// тіло відповіді
private T body;
// конструктори
public Response() {
}
public Response(int status, String exception, T body) {
this.status = status;
this.exception = exception;
this.body = body;
}
// методи getter та setter
...
}
- рядок 4: відповідь містить тип T;
- рядок 12: відповідь типу T;
- рядки 7–10: можливе виникнення винятку під час виконання методу. У цьому випадку метод поверне відповідь із:
- рядок 8: status!=0;
- рядок 10: повідомлення про помилку;
Клас [AbstractController] має такий вигляд:
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;
// шар DAO
private IDao<T> dao;
abstract protected IDao<T> getDao();
// локальний
private String simpleClassName = getClass().getSimpleName();
@PostConstruct
public void init(){
dao=getDao();
}
@Override
public Response<List<T>> getAllShortEntities() {
try {
// відповідь
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 {
// відповідь
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 {
// видаляємо
dao.deleteAllEntities();
// відповідь
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) {
...
}
}
Усі методи реалізовано однаково:
- якщо вони очікують на інформацію, то отримують її з об’єкта [HttpServletRequest request];
- вони викликають метод шару [DAO], який має таку саму назву, як і вони самі;
- вони обробляють винятки, які можуть виникнути або в операції 1 (отримання параметрів), або в операції 2 (виклик шару [DAO]);
Спочатку розглянемо, як шар [DAO] вбудовується в клас [AbstractController]:
public abstract class AbstractController<T extends AbstractCoreEntity> implements Iws<T> {
@Autowired
protected ApplicationContext context;
// шар DAO
private IDao<T> dao;
abstract protected IDao<T> getDao();
@PostConstruct
public void init(){
dao=getDao();
}
- рядок 1: клас є абстрактним і реалізує загальний інтерфейс [Iws<T>];
- рядки 3–4: ін'єкція контексту Spring;
- рядок 7: поки що невідоме посилання на шар [DAO], який потрібно використовувати;
- рядок 9: абстрактний метод [getDao], який поверне посилання на шар [DAO], який слід використовувати. Цей метод буде перевизначений дочірнім класом, і саме дочірній клас вкаже, який шар [DAO] слід використовувати (DaoProduit або DaoCategorie);
- рядок 11: анотація [@PostConstruct] позначає метод, який має бути виконаний після завершення інстанціювання об’єкта. Коли інстанціювання завершено, ін’єкції Spring вже виконано. Тоді дочірній клас отримає посилання на свій шар [DAO] і зможе передати його своєму батьківському класу;
Метод [getShortEntitiesById] має такий вигляд:
@Override
public Response<List<T>> getShortEntitiesById(HttpServletRequest request) {
try {
// отримуємо відправлене значення
String body = CharStreams.toString(request.getReader());
// десеріалізується
ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
List<Long> ids = mapper.readValue(body, new TypeReference<List<Long>>() {
});
// відповідь
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);
}
}
- рядок 5: значення, надіслане клієнтом, буде рядком jSON. Саме тут воно отримується;
- рядки 7–9: рядок jSON містить список первинних ключів об’єктів, для яких потрібна коротка версія;
- рядок 11: викликається однойменний метод [DAO]. Відповідь типу [List<T>] інкапсульована в об’єкт [Response];
- рядок 13: випадок, коли рівень [DAO] викликав виняток;
- рядок 15: випадок інших винятків, зокрема можливого винятку під час десеріалізації параметра jSON, рядок 8;
Метод [getShortEntitiesByName] є аналогічним:
@Override
public Response<List<T>> getShortEntitiesByName(HttpServletRequest request) {
try {
// отримуємо відправлене значення
String body = CharStreams.toString(request.getReader());
// десеріалізуємо її
ObjectMapper mapper = context.getBean("jsonMapper", ObjectMapper.class);
List<String> noms = mapper.readValue(body, new TypeReference<List<String>>() {
});
// відповідь
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);
}
}
- рядки 4–9: тут параметр jSON — це список назв категорій, для яких потрібна коротка версія;
Метод [saveEntities] не було реалізовано, оскільки він значною мірою залежить від характеру об’єкта, що зберігається, — [Categorie] або [Produit]. Коду, який потрібно факторизувати, небагато. Тому цю роботу доручено дочірнім класам.
@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. Контролер [CategorieController]
![]() |
Контролер [CategorieController] керує обробкою URL щодо таких категорій:
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;
}
// локальний
private String simpleClassName = getClass().getSimpleName();
@RequestMapping(value = "/getAllShortCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllShortCategories() {
// батьківський
Response<List<Categorie>> response = super.getAllShortEntities();
// фільтри серіалізації jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// відповідь
return response;
}
@RequestMapping(value = "/getAllLongCategories", method = RequestMethod.GET)
public Response<List<Categorie>> getAllLongCategories() {
// батьківський
Response<List<Categorie>> response = super.getAllLongEntities();
// фільтри серіалізації jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// відповідь
return response;
}
@RequestMapping(value = "/getShortCategoriesById", method = RequestMethod.POST)
public Response<List<Categorie>> getShortCategoriesById(HttpServletRequest request) {
// батьківський елемент
Response<List<Categorie>> response = super.getShortEntitiesById(request);
// фільтри серіалізації jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// відповідь
return response;
}
@RequestMapping(value = "/getShortCategoriesByName", method = RequestMethod.POST)
public Response<List<Categorie>> getShortCategoriesByName(HttpServletRequest request) {
// батьківський елемент
Response<List<Categorie>> response = super.getShortEntitiesByName(request);
// фільтри серіалізації jSON
context.getBean("jsonMapperShortCategorie", ObjectMapper.class);
// відповідь
return response;
}
@RequestMapping(value = "/getLongCategoriesById", method = RequestMethod.POST)
public Response<List<Categorie>> getLongCategoriesById(HttpServletRequest request) {
// батьківський елемент
Response<List<Categorie>> response = super.getLongEntitiesById(request);
// фільтри серіалізації jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// відповідь
return response;
}
@RequestMapping(value = "/getLongCategoriesByName", method = RequestMethod.POST)
public Response<List<Categorie>> getLongCategoriesByName(HttpServletRequest request) {
// батьківський елемент
Response<List<Categorie>> response = super.getLongEntitiesByName(request);
// фільтри серіалізації jSON
context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
// відповідь
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);
}
}
- рядок 26: клас [CategorieController] успадковує клас [AbstractController];
- рядок 25: анотація [@RestController] робить клас компонентом Spring. Ця анотація також вказує, що клас є веб-сервісом, методи якого безпосередньо надсилають відповідь клієнту у форматі jSON;
- рядки 28–29: тут вводиться посилання на шар [DAO];
- рядки 31–34: перевизначення методу [getDao], оголошеного абстрактним у батьківському класі, метою якого є повернення посилання на шар [DAO] для використання;
Усі методи побудовані за одним і тим самим зразком:
- делегування обробки батьківському класу;
- ініціалізація маппера jSON, який здійснить серіалізацію відповіді;
- відправлення відповіді;
Розглянемо сигнатуру деяких URL:
| - URL [/getAllShortCategories] викликається з GET |
| - URL [/getShortCategoriesById] викликається разом із POST. Значенням, що записується, є рядок jSON, що містить первинні ключі бажаних категорій; |
| - URL [/getLongCategoriesByName] викликається з POST. Значенням, що передається, є рядок jSON із назвами бажаних категорій; |
Тепер детально розглянемо метод [saveCategories], який не відповідає формату інших методів:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
// зберігаються категорії
try {
// отримуємо відправлене значення
String body = CharStreams.toString(request.getReader());
// десеріалізуємо її
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
// зберігаємо категорії
categories = daoCategorie.saveEntities(categories);
// повертаємо результат
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);
}
}
// результат
return new Response<List<CoreCategorie>>(0, null, coreCategories);
} catch (DaoException e) {
return new Response<List<CoreCategorie>>(1, e.toString(), null);
} catch (Exception e) {
return new Response<List<CoreCategorie>>(2, new ServerException(1020, e, simpleClassName).toString(), null);
}
}
- рядок 1: URL [/saveCategories] супроводжується значенням, що передається. Це рядок jSON, що містить повні назви категорій, які потрібно зберегти;
- рядки 5–10: категорії, які потрібно зберегти, відтворюються на основі рядка jSON. Посилання [produit.categorie], яке пов’язує [Produit] з його [Catégorie], має значення null, оскільки у повній версії [Categorie] кожен [Produit] сам є своєю скороченою версією без поля [categorie]. Це не створює проблем, оскільки рівень [DAO], реалізований за допомогою JDBC, не потребує цієї інформації;
- рядок 12: категорії збережено. Отриманий список категорій було доповнено первинними ключами збережених елементів — категорій та товарів. Більше нічого не змінилося. Замість того, щоб повертати весь отриманий список, що пов’язано з витратами, ми повернемо лише первинні ключі елементів цього списку. Для цього використовуються такі класи: [CoreCategorie] та [CoreProduit]:
![]() |
package spring.webjson.server.entities;
import java.util.List;
public class CoreCategorie {
// первинний ключ
private Long id;
// виробники
public CoreCategorie() {
}
public CoreCategorie(Long id) {
this.id=id;
}
// список товарів
private List<CoreProduit> coreProduits;
// методи getter та setter
...
}
- рядок 8: первинний ключ товару;
- рядок 20: первинні ключі його товарів;
package spring.webjson.server.entities;
public class CoreProduit {
// первинний ключ
private Long id;
// конструктори
public CoreProduit() {
}
public CoreProduit(Long id) {
this.id = id;
}
// методи getter та setter
...
}
- рядок 6: первинний ключ товару;
Повернемося до коду методу [saveCategories]:
...
// збереження категорій
categories = daoCategorie.saveEntities(categories);
// повертаємо результат
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);
}
}
// результат
return new Response<List<CoreCategorie>>(0, null, coreCategories);
...
- рядки 5–17: формується список [CoreCategorie], який буде повернуто віддаленому клієнту;
- рядок 19: відповідь повертається та серіалізується у jSON;
17.3.7. Управління фільтрами jSON
Для кожного методу контролера існує два етапи серіалізації/десеріалізації jSON:
- десеріалізація введеного значення: тут вона здійснюється явно;
- серіалізація результату: тут вона здійснюється неявно;
Почнемо з десеріалізації значення, відправленого в [CategorieController.saveCategories]:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(HttpServletRequest request) {
// збереження категорій
try {
// отримання відправленого значення
String body = CharStreams.toString(request.getReader());
// десеріалізуємо її
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
- рядок 8: у контексті Spring отримуємо маппер, налаштований для обробки фільтра jSON [jsonMapperLongCategorie]. Повернемося до визначення цього маппера у класі конфігурації [WebConfig]:
// -------------------------------- налаштування фільтрів [json]
// мапування 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);
}
// фільтри 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;
}
- рядки 32–40: мапер jSON [jsonMapperLongCategorie], отриманий класом [CategorieController];
- рядок 35: цей маппер повертається методом [jsonMapper] з рядків 18–21;
- рядки 18–21: метод [jsonMapper] повертає маппер jSON з конвертера [MappingJackson2HttpMessageConverter] з рядків 3–9;
Іншими словами, маппер jSON, отриманий у рядку 4 нижче, у [CategorieController.saveCategories]:
// отримуємо відправлене значення
String body = CharStreams.toString(request.getReader());
// десеріалізуємо його
ObjectMapper mapper = context.getBean("jsonMapperLongCategorie", ObjectMapper.class);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
є конвертером, який Spring використовує за замовчуванням (MVC) для десеріалізації значення, надісланого клієнтом, та серіалізації результату, що надсилається йому. У наведених вище рядках не відбулося неявного десеріалізування надісланого значення. Для цього слід було б написати:
@RequestMapping(value = "/saveCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreCategorie>> saveCategories(@RequestBody List<Categorie> categories) {
У цьому випадку відбулася б автоматична десеріалізація значення, надісланого в параметрі [categories]. Але виникла проблема з фільтром [jsonFilterCategorie], який мають сутності [Categorie]. Його потрібно налаштувати. Саме тому ми обрали явну десеріалізацію (рядки 4–5). Другий момент, на який слід звернути увагу, полягає в тому, що мапер у рядку 4 (який за замовчуванням використовується Spring MVC) також підходить для серіалізації результату [Response<List<CoreCategorie>]. Дійсно, суть [CoreCategorie] не має фільтра jSON. Тому немає необхідності налаштовувати отриманий мапер jSON із додатковим фільтром. У цьому випадку відбудеться неявна серіалізація відповіді, що надсилається клієнту.
17.3.8. Контролер [ProduitController]
![]() |
Контролер [ProduitController] керує обробкою URL, що стосуються товарів. Його код аналогічний коду контролера [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;
}
// локальний
private String simpleClassName = getClass().getSimpleName();
@RequestMapping(value = "/getAllShortProduits", method = RequestMethod.GET)
public Response<List<Produit>> getAllShortProduits() {
// батьківський
Response<List<Produit>> response = super.getAllShortEntities();
// фільтри серіалізації jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// відповідь
return response;
}
@RequestMapping(value = "/getAllLongProduits", method = RequestMethod.GET)
public Response<List<Produit>> getAllLongProduits() {
// батьківський
Response<List<Produit>> response = super.getAllLongEntities();
// фільтри серіалізації jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// відповідь
return response;
}
@RequestMapping(value = "/getShortProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getShortProduitsById(HttpServletRequest request) {
// батьківський елемент
Response<List<Produit>> response = super.getShortEntitiesById(request);
// фільтри серіалізації jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// відповідь
return response;
}
@RequestMapping(value = "/getShortProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getShortProduitsByName(HttpServletRequest request) {
// батьківський елемент
Response<List<Produit>> response = super.getShortEntitiesByName(request);
// фільтри серіалізації jSON
context.getBean("jsonMapperShortProduit", ObjectMapper.class);
// відповідь
return response;
}
@RequestMapping(value = "/getLongProduitsById", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getLongProduitsById(HttpServletRequest request) {
// батьківський елемент
Response<List<Produit>> response = super.getLongEntitiesById(request);
// фільтри серіалізації jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// відповідь
return response;
}
@RequestMapping(value = "/getLongProduitsByName", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<Produit>> getLongProduitsByName(HttpServletRequest request) {
// батьківський елемент
Response<List<Produit>> response = super.getLongEntitiesByName(request);
// фільтри серіалізації jSON
context.getBean("jsonMapperLongProduit", ObjectMapper.class);
// відповідь
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);
}
}
Лише метод [saveProduits] має структуру, відмінну від інших методів:
@RequestMapping(value = "/saveProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8")
public Response<List<CoreProduit>> saveProduits(HttpServletRequest request) {
try {
// отримуємо відправлене значення
String body = CharStreams.toString(request.getReader());
// десеріалізуємо його
ObjectMapper mapper = context.getBean("jsonMapperShortProduit", ObjectMapper.class);
List<Produit> produits = mapper.readValue(body, new TypeReference<List<Produit>>() {
});
// зберігаємо продукти
produits = daoProduit.saveEntities(produits);
List<CoreProduit> coreProduits = new ArrayList<CoreProduit>();
for (Produit produit : produits) {
coreProduits.add(new CoreProduit(produit.getId()));
}
// повертаємо відповідь
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);
}
}
- рядки 4–9: на основі отриманого рядка jSON відтворюється список [Produit], який потрібно зберегти. Оскільки отриманий рядок jSON відповідає коротким версіям продуктів, поле [categorie] для них дорівнює null. Знову ж таки, рівень DAO / JDBC не потребує цієї інформації;
- рядок 11: товари збережено;
- рядки 12–15: формується список [CoreProduit], що підлягають поверненню;
- рядок 18: повертається відповідь, яка буде серіалізована (неявна серіалізація, що здійснюється Spring MVC) за допомогою маппера з рядка 7 перед відправкою віддаленому клієнту (див. обговорення в параграфі 17.3.7);
17.3.9. Клас виконання веб-сервісу / jSON
![]() |
Клас [Boot] є класом виконання проекту:
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);
}
}
- рядок 10: виконується статичний метод [SpringApplication.run]. Клас [SpringApplication] є класом проекту [spring Boot] (рядок 3). Йому передаються два параметри:
- [AppConfig.class]: клас, який налаштовує весь додаток;
- [args]: можливі аргументи, передані методу [main] у рядку 9. Цей параметр тут не використовується;
Під час виконання цього класу з’являються такі записи в журналі:
- рядки 11–15: виявлені біни, що визначають фільтри jSON. Вони перевизначають біни з такими самими іменами, виявлені в проекті конфігурації шару JDBC;
- рядки 17–18: запуск сервера Tomcat, який буде виконувати веб-сервіс / jSON;
- рядки 19–21: ініціалізується контекст Spring MVC;
- рядки 24–43: виявлення експонованих URL;
17.3.10. Тестування веб-сервісу / jSON
Для проведення тестування ми використовуємо клієнт [Advanced Rest Client] (див. параграф 23.11) для запиту до URL, які надаються веб-сервісом / jSON (веб-сервіс / jSON, звичайно, має бути запущений, так само як і SGBD). Щоб заповнити базу даних, ми виконуємо конфігурацію виконання під назвою [spring-jdbc-generic-04-fillDataBase], яка заповнює базу даними про 5 категорій та 10 товарів:
![]() |
![]() |
- у [1-3] ми запитуємо URL та [/getAllLongCategories] за допомогою команди HTTP та GET;
Ми отримуємо таку відповідь:
![]() |
- у [1] — запит HTTP від клієнта;
- у [2] — відповідь HTTP від сервера;
- у [3] статус [200 OK] вказує, що сервер успішно обробив запит;
- у [4] — відповідь сервера jSON;
Повна відповідь jSON має такий вигляд:
{"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 означає, що на стороні сервера помилок не було;
- exception: null означає, що повідомлення про помилку відсутнє;
- body: це тіло відповіді, у даному випадку — список категорій із відповідними товарами. Є дві категорії, у кожній з яких по 5 товарів;
Ми додамо до категорії [categorie1] товар [produit15]. Для цього ми використаємо URL та [/saveProduits], які очікують на ланцюжок jSON з товарами, що підлягають збереженню (додавання/зміна). Цей ланцюжок матиме такий вигляд:
[{"id":null,"version":null,"nom":"produit15","idCategorie":1881,"prix":111.0,"description":"desc15"}]}]
Запит до веб-сервісу / jSON формується наступним чином:
![]() |
- у [1] — запитується URL;
- у [2] запитується через операцію POST;
- у [3] — надіслано рядок jSON;
- у [4] серверу повідомляється, що йому буде надіслано jSON;
Відповідь сервера така:
![]() |
- у [1] отримано список [CoreProduit] із їхніми первинними ключами. Тут отримано список з одного елемента з первинним ключем товару, який щойно було вставлено до бази даних;
Тепер запитаємо розгорнуту версію категорії з назвою [categorie[1]:
![]() |
- у [1] замість запитуваного URL;
- в [2] ми отримуємо POST;
- у [3,4] значення, що передається, — це рядок jSON. Він представляє список назв категорій, для яких потрібна розгорнута версія;
Ми отримуємо такий результат:
![]() |
- у [5] категорія [categorie[1]] тепер має шостий товар;
Тепер видалимо цей товар:
![]() |
- в [1], запитуваний URL;
- в [2] створюємо POST;
- у [3-4] надсилається рядок jSON, що містить список первинних ключів продуктів, які потрібно видалити;
Отриманий результат такий:
![]() |
- [status:0] вказує, що видалення пройшло успішно;
Тепер запитаємо товар [produit[1,5]], щоб перевірити, чи він дійсно був видалений:
![]() |
Ми отримуємо такий результат:
![]() |
- [status:0] вказує, що операція пройшла без винятків;
- [body:[0]] вказує, що [body] є списком із 0 елементів. Отже, об’єкт [produit[1,5]] було успішно видалено;
Усі операції з [GET] можна виконати у звичайному веб-браузері:
![]() |
Читачеві пропонується протестувати інші URL веб-сервісу / jSON.








































