JavaRush /Java блог /Random UA /Spring - це не страшно, прошарок з DTO
Павел
11 рівень

Spring - це не страшно, прошарок з DTO

Стаття з групи Random UA
ЗМІСТ ЦИКЛУ СТАТТІВ Продовжуємо говорити про Spring. Сьогодні розбиратимемо патерн DTO, для розуміння можна почитати тут . Найскладніше в DTO – це зрозуміти, навіщо воно потрібне. Давайте займемося спекуляцією овочів, і заразом, попишемо код, може по ходу справи щось прояснитися. Створіть spring-boot проект , підключіть h2 і Lombok . Створіть пакети: entities, repositories, services, utils. В entities створіть сутність Product:
package ru.java.rush.entities;

import lombok.Data;
import lombok.experimental.Accessors;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Accessors(chain = true)
@Entity
@Data
public class ProductEntity {

    @Id
    @Column
    @GenericGenerator(name = "generator", strategy = "increment")
    @GeneratedValue(generator = "generator")
    Integer id;

    @Column
    String name;

    @Column
    Integer purchasePrice;//закупочная цена

}
Реалізуйте класи ProducRepository, ProducService та клас ItiiateUtil ( аналогічно минулій статті ). Допустимо ми прикупабо картопля за оптовою ціною 20 рублів за кг., і моркви по 14 рублів за кг. Придбані продукти покладемо у сховище. Доповнимо БД записами: [Id = 1, name = "Картопля", purchasePrice = 20] [Id = 2, name = "Морква", purchasePrice = 14] Як порядні спекулянти, ми повинні вигідно впарити свій товар, для цього давайте красиво запакуємо його і накрутимо ціну. Тобто були у нас брудні і не гарні овочі, навалені купою, а стануть чистенькі преміум-веган продукти сегменту лакшері. Погодьтеся, це буде вже не той продукт який ми купабо оптом. Для нового продукту створимо пакет dto та в ньому клас ProductDto
package ru.java.rush.dto;

import lombok.Data;

@Data
public class ProductDto {
    Integer id;
    String name;
    Integer purchasePrice;
    String  packaging;//упаковка
    Integer salePrice;//цена реализации
}
ProductDto має дві змінні, яких немає у ProductEntity: «упаковка» і «ціна реалізації». Об'єкт dto може містити такі самі змінні, як і entity, або їх може бути більше, або менше. Ми пам'ятаємо, що конвертація одного об'єкта в інший – це справа мапінгу. У пакеті utils створимо клас MappingUtils
package ru.java.rush.utils;

import org.springframework.stereotype.Service;
import ru.java.rush.dto.ProductDto;
import ru.java.rush.entities.ProductEntity;

@Service
public class MappingUtils {
//из entity в dto
    public ProductDto mapToProductDto(ProductEntity entity){
        ProductDto dto = new ProductDto();
        dto.setId(entity.getId());
        dto.setName(entity.getName());
        dto.setPurchasePrice(entity.getPurchasePrice());
        return dto;
    }
//из dto в entity
    public ProductEntity mapToProductEntity(ProductDto dto){
        ProductEntity entity = new ProductEntity();
        entity.setId(dto.getId());
        entity.setName(dto.getName());
        entity.setPurchasePrice(dto.getPurchasePrice());
        return entity;
    }
}
Просто заповнюємо поля одного об'єкта, аналогічними полями з іншого об'єкта. У класі ProductService реалізуємо методи для пошуку одного продукту або списку продуктів, але перед цим ми конвертуємо entity в dto за допомогою написаного вище методу.
private final ProductRepository productRepository;
private final MappingUtils mappingUtils;


//для листа продуктов мы использовали стрим
public List<ProductDto> findAll() {
    return productRepository.findAll().stream() //создали из листа стирим
            .map(mappingUtils::mapToProductDto) //оператором из streamAPI map, использовали для каждого елемента метод mapToProductDto из класса MappingUtils
.collect(Collectors.toList()); //превратабо стрим обратно в коллекцию, а точнее в лист
}

//для одиночного продукта обошлись проще
public ProductDto findById(Integer id) {
    return mappingUtils.mapToProductDto( //в метод mapToProductDto
            productRepository.findById(id) //поместабо результат поиска по id
                    .orElse(new ProductEntity()) //если ни чего не нашли, то вернем пустой entity
    );
}
Що буде, якщо ми зараз покладемо ці овочі на вітрину? А давайте подивимося. Для цього в ItiiateUtil напишемо наступний код і запустимо.
System.out.println("\nВитрина магазина");
for (ProductDto dto : productService.findAll()) {
    System.out.println(dto);
}
На виході отримаємо: Вітрина магазину ProductDto(id=1, name=Картопля, purchasePrice=20, packaging=null, salePrice=null) ProductDto(id=2, name=Морква, purchasePrice=14, packaging=null, salePrice=null) Ну вже немає! Такі овочі ніхто не придбає: брудні, не упаковані, та й ціна продажу не відома. Настав час бізнес-логіки. Її реалізуємо у класі ProductService. Додамо спочатку в цей клас пару змінних:
private final Integer margin = 5;//это наша накрутка на цену
private final String packaging = "Упаковано в лучшем виде";//так будет выглядеть упаковка
Для кожної дії: упаковка та накрутка ціни – створимо в цьому ж класі за окремим методом:
// упаковываем товар
public void pack(List<ProductDto> list) {
    list.forEach(productDto ->
            productDto.setPackaging(packaging)
    );
}

// делаем деньги
public void makeMoney(List<ProductDto> list) {
    list.forEach(productDto ->
            productDto.setSalePrice(productDto.getPurchasePrice() * margin)
    );
}
Повертаємося до ItiiateUtil і викладання на вітрину замінюємо на наступний код
List<ProductDto> productDtos = productService.findAll();

productService.pack(productDtos);
productService.makeMoney(productDtos);

System.out.println("\nВитрина магазина");
for (ProductDto dto : productDtos)) {
    System.out.println(dto);
}
Виконуємо: Вітрина магазину ProductDto(id=1, name=Картопля, purchasePrice=20, packaging=Упаковано у найкращому вигляді, salePrice=100) ProductDto(id=2, name=Морква, purchasePrice=14, packaging=Упаковано у найкращому вигляді, salePrice=70) Товар красиво упакований, є ціна, але ви де-небудь бачабо, щоб на вітрині писали ціну за яку купабо оптом і ще id якийсь. Допрацьовуємо напилком, написаний вище код:
List<ProductDto> productDtos = productService.findAll();

productService.pack(productDtos);
productService.makeMoney(productDtos);

System.out.println("\nВитрина магазина");
for (ProductDto dto : productDtos) {
    System.out.println(String.format(
            "Купите: %s , по цене:  %d", dto.getName(), dto.getSalePrice()
    ));
}
class InitiateUtils в результаті повинен бути таким:
@Service
@RequiredArgsConstructor
public class InitiateUtils implements CommandLineRunner {

    private final ProductService productService;

    @Override
    public void run(String... args) throws Exception {

        List<ProductEntity> products = new ArrayList<>(
                Arrays.asList(
                        new ProductEntity()
                                .setName("Картофель")
                                .setPurchasePrice(20),
                        new ProductEntity()
                                .setName("Морковь")
                                .setPurchasePrice(14)
                ));

        productService.saveAll(products);

        List<ProductDto> productDtos = productService.findAll();

        productService.pack(productDtos);
        productService.makeMoney(productDtos);

        System.out.println("\nВитрина магазина");
        for (ProductDto dto : productDtos) {
            System.out.println(String.format(
                    "Купите: %s , по цене:  %d", dto.getName(), dto.getSalePrice()
            ));
        }
    }
}
Запускаємо: Вітрина магазину Купуйте: Картопля, за ціною: 100 Купуйте: Морква, за ціною: 70 Інша справа! Тепер подумаємо, що dto принесло хорошого, крім купи додаткового коду: 1. Ми можемо здійснювати бізнес-логіку не змінюючи об'єкти в БД (припустимо, ну непотрібно нам у цій таблиці мати поля про упаковку та ціну продажу). Картопля відмінно лежить у сховищі та без упаковки з цінником, вони там навіть зайві. 2. У цьому рядку List<ProductDto> productDtos = productService.findAll()ми створабо кеш з об'єктів, з якими зручно працювати в рамках бізнес-логіки. Це, якби ми поклали частину товару у підсобку магазину. 3. Це нам дозволило зробити дві бізнес дії: упаковка і націнка, але запит в базу зробабо тільки один раз (запити в базу досить важкі в плані продуктивності). Товар можна упаковувати, клеїти цінник і викладати на вітрину – поступово набираючи його з підсобки, а не бігати за ним щоразу у сховище. На запитання: «Навіщо так складно?» люди теж намагаються знайти відповідь, почитайте . Наступна стаття
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ