JavaRush /Java Blog /Random EN /Spring is not scary, a layer of DTO
Павел
Level 11

Spring is not scary, a layer of DTO

Published in the Random EN group
CONTENTS OF THE ARTICLE CYCLE We continue to talk about Spring. Today we will analyze the DTO pattern, for understanding you can read here . The hardest part about DTOs is understanding why they are needed. Let's get into the speculation of vegetables, and at the same time, write the code, maybe something will become clear along the way. Create a spring-boot project , connect h2 and Lombok . Create packages: entities, repositories, services, utils. In entities, create a Product entity:
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;//закупочная цена

}
Implement the ProducRepository, ProducService classes and the ItiiateUtil class ( similar to the previous article ). Suppose we bought potatoes at a wholesale price of 20 rubles per kg, and carrots at 14 rubles per kg. We put the purchased products in storage. Let's supplement the database with records: [Id =1, name= “Potato”, purchasePrice = 20] [Id =2, name= “Carrot”, purchasePrice = 14] As decent speculators, we must profitably sell our goods, for this, let's pack beautifully it and wind up the price. That is, we had dirty and not beautiful vegetables piled in a heap, but clean premium vegan products of the luxury segment will become. Agree, this will not be the same product (object) that we bought in bulk. For a new product, create a dto package and the ProductDto class in it
package ru.java.rush.dto;

import lombok.Data;

@Data
public class ProductDto {
    Integer id;
    String name;
    Integer purchasePrice;
    String  packaging;//упаковка
    Integer salePrice;//цена реализации
}
ProductDto has two variables that ProductEntity does not have: "packaging" and "distribution price". The dto object can contain exactly the same variables as the entity, or there can be more or less of them. We remember that converting one object to another is a matter of mapping. In the utils package, create the MappingUtils class
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;
    }
}
We just fill in the fields from one object with similar fields from another object. In the ProductService class, we implement methods to search for a single product or a list of products, but before that, we convert the entity to a dto using the method written above.
private final ProductRepository productRepository;
private final MappingUtils mappingUtils;


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

//для одиночного продукта обошлись проще
public ProductDto findById(Integer id) {
    return mappingUtils.mapToProductDto( //в метод mapToProductDto
            productRepository.findById(id) //поместor результат поиска по id
                    .orElse(new ProductEntity()) //если ни чего не нашли, то вернем пустой entity
    );
}
What will happen if we put these vegetables in the window now? And let's see. To do this, write the following code in ItiiateUtil and run it.
System.out.println("\nВитрина магазина");
for (ProductDto dto : productService.findAll()) {
    System.out.println(dto);
}
The output will be: Storefront ProductDto(id=1, name=Potato, purchasePrice=20, packaging=null, salePrice=null) ProductDto(id=2, name=Carrot, purchasePrice=14, packaging=null, salePrice=null) Well, I do not! No one will buy such vegetables: they are dirty, not packaged, and the sale price is not known. It's time for business logic. We implement it in the ProductService class. Let's first add a couple of variables to this class:
private final Integer margin = 5;//это наша накрутка на цену
private final String packaging = "Упаковано в лучшем виде";//так будет выглядеть упаковка
For each action: packaging and price increase, we will create a separate method in the same class:
// упаковываем товар
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)
    );
}
We return to ItiiateUtil and replace the showcase with the following code
List<ProductDto> productDtos = productService.findAll();

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

System.out.println("\nВитрина магазина");
for (ProductDto dto : productDtos)) {
    System.out.println(dto);
}
Execute: Storefront ProductDto(id=1, name=Potato, purchasePrice=20, packaging=Packed in the best possible way, salePrice=100) ProductDto(id=2, name=Carrot, purchasePrice=14, packaging=Packed in the best possible way, salePrice=70) The product is beautifully packaged, there is a price, but have you seen somewhere that they would write on the window the price for which they bought in bulk and some other id. We finalize the code written above with a file:
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 should end up like this:
@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()
            ));
        }
    }
}
Launching: Storefront Buy: Potatoes, Price: 100 Buy: Carrots, Price: 70 Another thing! Now we think that dto has brought good, except for a bunch of additional code: 1. We can perform business logic without changing objects in the database (let's say it's unnecessary for us to have fields about packaging and sale price in this table). Potatoes will lie perfectly in storage even without packaging with a price tag, they are even superfluous there. 2. In this line List<ProductDto> productDtos = productService.findAll()we have created a cache of objects that are convenient to work with within the business logic. This is if we put part of the goods in the back of the store. 3. This allowed us to perform two business actions: packaging and margin, but the request to the database was made only once (requests to the database are quite heavy in terms of performance). The goods can be packed, a price tag glued and put on a showcase - gradually picking it up from the back room, and not running after it every time in the store. To the question: “Why is it so difficult?”, People are also trying to find an answer, read . Next article
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION