Рубрики
Без рубрики

Создайте Spring Boot ReST API

С возвращением! В первой статье блога мы разработали простое приложение Spring Boot, которое печатает “H… С тегами spring, api, java, jpa.

С возвращением! В первой статье блога мы разработали простое приложение Spring Boot, которое выводит “Hello World” на консоль. В этой статье мы создадим ReST API, который может вызываться клиентами API.

В типичном приложении Spring Boot у вас будет реляционная база данных с несколькими таблицами, связанными друг с другом. В этой статье, чтобы упростить задачу, мы сосредоточимся на приложении с одной таблицей. В следующей статье мы расширим этот пример, включив в него несколько таблиц и их взаимосвязи.

Давайте представим, что мы создаем простое приложение для составления расписания. Это приложение позволяет сотрудникам вводить часы, которые они ежедневно работали над различными задачами, и отправлять табель учета рабочего времени каждые две недели.

В этой статье мы сосредоточимся на создании ReST API только для таблицы Customer.

Давайте предположим, что таблица клиентов в базе данных содержит следующие поля:

*идентификатор клиента *имя *описание * неактивный

Наша цель – разработать ReST API, который можно вызвать для выполнения следующих операций:

*Создайте нового клиента * Получить список всех клиентов * Обновить существующего клиента *Удалить существующий клиент

Вышеуказанные операции известны как операции C.R.U.D (Создание, Чтение, Обновление, Удаление).

Шаг-1 :

Во-первых, нам нужно понять некоторые основные понятия о том, как мы можем манипулировать данными в базе данных из загрузочного приложения Java/Spring. Это можно сделать либо через API JDBC (Java Database Connectivity), который является частью Java Standard Edition, либо через JPA (Java Persistence API; теперь переименован в Jakarta Persistence). это часть Java Enterprise Edition.

Spring framework предоставляет дополнительные абстракции для этих API, чтобы разработчикам было проще использовать эти API. В этой статье мы сосредоточимся на использовании JPA.

JPA описывает управление реляционными данными в корпоративных приложениях Java. Hibernate Object Relational Mapper (ORM) является одним из разработчиков Java Persistence API. В этом приложении мы будем использовать Hibernate ORM и его реализацию JPA.

В JPA таблица базы данных представлена в виде класса Java. JPA предоставляет механизм сохранения объектов этого класса Java в таблице базы данных, а строк таблицы базы данных обратно в объекты этого класса Java. Существует ряд правил, которые определяются JPA в отношении того, как должна быть определена сущность. В соответствии со спецификацией JPA 2.0, эти правила следующие:

  1. Класс должен быть аннотирован с помощью @Entity (javax.persistence. Сущность) аннотация.

  2. Каждый класс сущностей должен иметь первичный ключ, который однозначно идентифицирует сущность. Первичный ключ аннотируется с помощью @id аннотации

  3. Класс entity должен иметь конструктор без аргументов. У него могут быть и другие конструкторы. Конструктор no-arg должен быть общедоступным или защищенным.

  4. Класс сущности должен быть классом верхнего уровня. Перечисление или интерфейс не должны быть обозначены как объект.

  5. Класс сущности не должен быть окончательным. Никакие методы или постоянные переменные экземпляра класса entity не могут быть окончательными.

  6. Постоянные переменные экземпляра должны быть объявлены частными, защищенными или закрытыми для пакетов, и к ним можно получить прямой доступ только с помощью методов класса entity. Клиенты должны получить доступ к состоянию объекта с помощью средств доступа или бизнес-методов.

  7. Если экземпляр сущности должен передаваться по значению как отдельный объект (например, через удаленный интерфейс), класс сущности должен реализовать сериализуемый интерфейс.

  8. Сущностями могут быть как абстрактные, так и конкретные классы. Сущности могут расширять классы, не являющиеся сущностями, а также классы сущностей, а классы, не являющиеся сущностями, могут расширять классы сущностей.

Используя приведенные выше правила, давайте определим нашу сущность Клиента следующим образом:

package com.example.demo;

import javax.persistence.*;
import java.io.Serializable;

@Entity
@Table(name = "customer")
public class CustomerEntity implements Serializable {

   public CustomerEntity() {}

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 @Column(name = "customerid", nullable = false)
 private Long customerid;

 @Basic
 @Column(name = "name", nullable = true)
 private String name;

 @Basic
 @Column(name = "description", nullable = true)
 private String description;

 @Basic
 @Column(name = "isactive", nullable = true)
 private Boolean isActive;

 public Long getCustomerid() {
   return customerid;
 }

 public void setCustomerid(Long customerid) {
   this.customerid = customerid;
 }

 public String getName() {
   return name;
 }

 public void setName(String name) {
   this.name = name;
 }

 public String getDescription() {
   return description;
 }

 public void setDescription(String description) {
   this.description = description;
 }

 public Boolean getIsActive() {
   return isactive;
 }

 public void setIsActive(Boolean isactive) {
   this.isActive = isActive;
 }
}

Моменты, на которые следует обратить внимание :

  1. Аннотация @id определяет первичный ключ. Мы можем генерировать идентификаторы различными способами, которые указаны в аннотации @GeneratedValue. Мы можем выбрать одну из следующих стратегий генерации идентификаторов: АВТО, ТАБЛИЦА, ПОСЛЕДОВАТЕЛЬНОСТЬ или ИДЕНТИФИКАТОР. Если мы не указываем значение явно, тип генерации по умолчанию имеет значение AUTO. Когда мы используем тип генерации идентификатора, база данных автоматически увеличивает значения идентификатора

  2. Также важно понимать различные типы доступа в JPA. В этой статье содержится более подробная информация о типах доступа. В приведенной выше сущности customer мы используем тип доступа к полю, потому что мы объявляем аннотацию @id для поля, а не для метода get access.

  3. Если мы не используем аннотацию @Table, имя объекта будет считаться именем таблицы. В таких случаях мы можем опустить аннотацию @Table. В большинстве случаев имя таблицы в базе данных и имя объекта не будут совпадать. В этих случаях мы можем указать имя таблицы, используя аннотацию @Table

  4. Атрибуты аннотации @Column включают ее имя, длину, возможность обнуления и уникальность. Атрибут name задает имя столбца таблицы. Атрибут length задает длину столбца. Атрибут nullable указывает, является ли столбец nullable или нет, а атрибут unique указывает, является ли столбец уникальным или нет. Если мы не укажем эту аннотацию, имя поля будет считаться именем столбца в таблице.

  5. JPA поддерживает типы данных Java в виде постоянных полей сущности, часто называемых базовыми типами. Поле или свойство может быть помечено символом @basic аннотация, которая является необязательной. Аннотация @basic/| имеет два атрибута – необязательный (true, false) и выборка (НЕТЕРПЕЛИВЫЙ, ЛЕНИВЫЙ). По умолчанию для параметра optional установлено значение true, а для параметра fetch установлено значение нетерпеливой загрузки. Если для параметра optional установлено значение true, поле/свойство будет принимать значения null. Отложенная загрузка будет иметь смысл только тогда, когда у нас есть большой сериализуемый объект, отображенный как базовый тип. Спецификация JPA строго ограничивает типы Java, которые могут быть помечены как базовые, следующими:

Java примитивные типы (boolean, int и т.д.) Оболочки для примитивных типов (java.lang. Логическое значение, java.lang. Целое число и т.д.) java.lang. Строка java.math. BigInteger java.математика. BigDecimal java.util. Дата java.util. Календарь java.sql. Дата java.sql. Время java.sql. Временная метка byte[] Byte[] char[] Character[] перечисляет любой другой тип, который реализует сериализуемый

N-уровневая архитектура

Чтобы создать CRUD API для таблицы базы данных клиентов, мы будем использовать n-уровневую архитектуру. Эта архитектура не является необходимой для приложения с одной таблицей базы данных, но становится необходимой по мере увеличения количества таблиц и добавления в приложение большего количества бизнес-логики.

На приведенной ниже диаграмме показаны уровни этой архитектуры:

Доменный уровень : Уровень домена состоит из классов домена. В нашем примере приложения CRUD эти классы домена эквивалентны классам сущностей JPA, которые представляют собой единый класс с именем CustomerEntity.java, который мы ранее описали.

Уровень хранилища : Уровень репозитория в приложении Spring Boot состоит из Spring Data JPA репозиториев. Spring Boot очень упрощает разработку этих репозиториев, которые могут позволить вам выполнять базовые операции CRUD над сущностью. Вот репозиторий JPA для объекта клиента:

@Repository("customerRepository")
public interface ICustomerRepository extends JpaRepository{}

Все, что нам нужно сделать для создания репозитория, – это определить интерфейс, который расширяет интерфейс JpaRepository, передавая имя объекта и тип данных для первичного ключа объекта.

Spring Boot автоматически предоставляет класс реализации для этого интерфейса. Таким образом, все методы, указанные в интерфейсе JpaRepository по этой ссылке , доступны для использования нами. Также обратите внимание, что этот интерфейс снабжен комментариями @Repository interface. Более подробную информацию об этом и других часто используемых аннотациях можно найти в this article.

Уровень служб приложений : Этот уровень используется для двух целей: (а) обеспечения поддержки транзакций и (б) преобразования из сущности в объект передачи данных (DTO) и наоборот – наоборот.

Транзакция приложения – это последовательность действий приложения, которые рассматриваются приложением как единое логическое целое. Для более подробного объяснения транзакций Spring, пожалуйста, обратитесь к этим сериям сообщений и этому другому сообщению для подробного рассмотрения подводных камней.

DTO важны для разделения проблем – отделения того, что хранится в базе данных, от того, что возвращается клиенту API. Сущность хранит то, что хранится в базе данных, в то время как объект TO хранит то, что возвращается обратно клиенту API. Для получения более подробного объяснения того, почему DTO необходимы, пожалуйста, обратитесь к this post.

Снова придерживаясь принципа разделения задач, мы определяем как интерфейс, так и класс реализации на уровне служб приложений для объекта-клиента. Этот уровень будет вызывать уровень репозитория JPA, который мы определили ранее.

ICustomerAppService.java

public interface ICustomerAppService {

//CRUD Operations
CreateCustomerOutput create(CreateCustomerInput customer);

void delete(Long id);

UpdateCustomerOutput update(Long id, UpdateCustomerInput input);

FindCustomerByIdOutput findById(Long id);

}

CustomerAppService.java

@Service("customerAppService")
public class CustomerAppService implements ICustomerAppService {

 @Qualifier("customerRepository")
 @NonNull
 protected final ICustomerRepository _customerRepository;

 @Qualifier("ICustomerMapperImpl")
 @NonNull
 protected final ICustomerMapper mapper;

 @NonNull
 protected final LoggingHelper logHelper;

 public CustomerAppService(@NonNull ICustomerRepository 
 _customerRepository, @NonNull ICustomerMapper mapper, 
 @NonNull LoggingHelper logHelper) {
 this._customerRepository = _customerRepository;
 this.mapper = mapper;
 this.logHelper = logHelper;
 }

 @Transactional(propagation = Propagation.REQUIRED)
 public CreateCustomerOutput create(CreateCustomerInput input) {

 CustomerEntity customer = mapper.createCustomerInputToCustomerEntity(input);
 CustomerEntity createdCustomer = _customerRepository.save(customer);
 return mapper.customerEntityToCreateCustomerOutput(createdCustomer);
 }

 @Transactional(propagation = Propagation.REQUIRED)
 public UpdateCustomerOutput update(Long customerId, 
 UpdateCustomerInput input) {

 CustomerEntity customer = mapper.updateCustomerInputToCustomerEntity(input);
 CustomerEntity updatedCustomer = _customerRepository.save(customer);
 return mapper.customerEntityToUpdateCustomerOutput(updatedCustomer);
 }

 @Transactional(propagation = Propagation.REQUIRED)
 public void delete(Long customerId) {

 CustomerEntity existing = _customerRepository.findById(customerId).orElse(null);
 _customerRepository.delete(existing);
 }

 @Transactional(propagation = Propagation.NOT_SUPPORTED)
 public FindCustomerByIdOutput findById(Long customerId) {

 CustomerEntity foundCustomer = _customerRepository.findById(customerId).orElse(null);
 if (foundCustomer == null) return null;
 return mapper.customerEntityToFindCustomerByIdOutput(foundCustomer);

Моменты, которые следует отметить :

  1. Методы CRUD определены в классе application service

  2. Класс аннотируется аннотацией @Service. Чтобы понять эту аннотацию, пожалуйста, обратитесь к this article.

  3. Класс службы приложений зависит от трех других классов – CustomerRepository, Customer Mapper и Logging Helper. Эти зависимости вводятся в класс службы приложения с помощью механизма внедрения зависимостей Spring.

Spring позволяет нам вводить зависимости с помощью трех методов – инъекции на основе конструктора, инъекции на основе установщика или инъекции на основе поля. Как объясняется в this article, внедрение на основе конструктора является предпочтительным способом внедрения зависимостей.

  1. При внедрении на основе конструктора зависимости, необходимые для класса, предоставляются в качестве аргументов конструктору, как показано в службе приложений выше.

  2. Аннотация @Transactional используется на уровне метода для общедоступных методов

  3. Мы определяем и используем следующие DTO. Каждый метод, где это уместно, принимает входные данные и возвращает выходной DTO

**Создать ввод данных клиента *Createcustomeroutput *Updatecustomerinput *Updatecustomeroutput *Findcustomerbyidoutput

Мы используем картографическую библиотеку, MapStruct , чтобы автоматически сопоставить объект с DTO и наоборот.

Уровень контроллера ReST : Этот уровень используется для представления интерфейса для клиентов, которые хотят выполнять операции CRUD с данными в таблицах базы данных с использованием протокола http. В нашем примере приложения у нас есть один RestController, потому что у нас есть одна сущность/таблица.

@RestController
@RequestMapping("/customer")
public class CustomerController {

 @Qualifier("customerAppService")
 @NonNull
 protected final ICustomerAppService _customerAppService;

 @Qualifier("projectAppService")
 @NonNull
 protected final IProjectAppService _projectAppService;

 @NonNull
 protected final LoggingHelper logHelper;

 @NonNull
 protected final Environment env;

 public CustomerController(@NonNull ICustomerAppService _customerAppService, @NonNull IProjectAppService 
 _projectAppService, @NonNull LoggingHelper logHelper, @NonNull Environment env) {
 this._customerAppService = _customerAppService;
 this._projectAppService = _projectAppService;
 this.logHelper = logHelper;
 this.env = env;
 }

 @RequestMapping(method = RequestMethod.POST, consumes = { "application/json" }, produces = { "application/json" })
 public ResponseEntity create(@RequestBody @Valid CreateCustomerInput customer) {
 CreateCustomerOutput output = _customerAppService.create(customer);
 return new ResponseEntity(output, HttpStatus.OK);
 }

 // ------------ Delete customer ------------
 @ResponseStatus(value = HttpStatus.NO_CONTENT)
 @RequestMapping(value = "/{id}", method = RequestMethod.DELETE, 
 consumes = { "application/json" })
 public void delete(@PathVariable String id) {
 FindCustomerByIdOutput output = _customerAppService.findById(Long.valueOf(id));
 Optional
 .ofNullable(output)
 .orElseThrow(
 () -> new EntityNotFoundException(String.format("There does not exist a customer with a id=%s", id))
 );

 _customerAppService.delete(Long.valueOf(id));
 }

 // ------------ Update customer ------------
 @RequestMapping(
 value = "/{id}",
 method = RequestMethod.PUT,
 consumes = { "application/json" },
 produces = { "application/json" }
 )

 public ResponseEntity update(
 @PathVariable String id,
 @RequestBody @Valid UpdateCustomerInput customer
 ) {
 FindCustomerByIdOutput currentCustomer = _customerAppService.findById(Long.valueOf(id));
 Optional
 .ofNullable(currentCustomer)
 .orElseThrow(
 () -> new EntityNotFoundException(String.format("Unable to update. Customer with id=%s not found.", id))
 );

 customer.setVersiono(currentCustomer.getVersiono());
 UpdateCustomerOutput output = _customerAppService.update(Long.valueOf(id), customer);
 return new ResponseEntity(output, HttpStatus.OK);
 }

 @RequestMapping(
 value = "/{id}",
 method = RequestMethod.GET,
 consumes = { "application/json" },
 produces = { "application/json" }
 )
 public ResponseEntity findById(@PathVariable String id) {
 FindCustomerByIdOutput output = _customerAppService.findById(Long.valueOf(id));
 Optional.ofNullable(output).orElseThrow(() -> new EntityNotFoundException(String.format("Not found")));

 return new ResponseEntity(output, HttpStatus.OK);
 }
}

Моменты, которые следует отметить :

  1. Мы передаем зависимости с помощью Spring Constructor dependency injection

  2. Каждый метод контроллера ReST имеет аннотацию @RequestMapping, тип метода и тип содержимого, который использует и создает метод.

  3. Там, где мы возвращаем ответ, мы используем ResponseEntity Хотя из приведенного выше кода это не очевидно, входящее содержимое JSON преобразуется в объект Java с использованием библиотеки сопоставления Jackson

В этой статье в блоге мы рассмотрели, как создать Spring Boot ReST API для одной таблицы. Однако в реальном мире API будет создан с использованием нескольких таблиц, связанных друг с другом. В следующей (третьей) статье блога мы обсудим, как мы можем обрабатывать несколько таблиц.

Оригинал: “https://dev.to/fastcodeinc/build-a-spring-boot-rest-api-1p8c”