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

Spring is not scary, a DTO layer

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 thing about DTO is understanding why it is needed. Let's start speculating on vegetables, and at the same time, write some code, maybe something will become clearer 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 ). Let’s say we bought potatoes at a wholesale price of 20 rubles per kg, and carrots at 14 rubles per kg. We will place the purchased products in storage. Let’s add the following records to the database: [Id =1, name= “Potatoes”, purchasePrice = 20] [Id =2, name= “Carrots”, purchasePrice = 14] As decent speculators, we must profitably sell our goods, for this let’s package it beautifully it and we’ll increase the price. That is, we had dirty and unattractive vegetables piled up in a heap, but there will be clean premium vegan products from the luxury segment. Agree, this will no longer be the same product (object) that we bought in bulk. For a new product, create a dto package and in it the class ProductDto
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: “packaging” and “selling price”. The dto object may contain exactly the same variables as the entity, or there may be more or fewer of them. We remember that converting one object to another is a matter of mapping. In the utils package we will 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 simply 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 now put these vegetables on the display? 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 is: Store display ProductDto(id=1, name=Potatoes, purchasePrice=20, packaging=null, salePrice=null) ProductDto(id=2, name=Carrots, purchasePrice=14, packaging=null, salePrice=null) Well, I do not! No one will buy such vegetables: they are dirty, not packaged, and the selling price is unknown. 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 markup, 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 display on the display 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);
}
We carry out: Store display ProductDto(id=1, name=Potatoes, purchasePrice=20, packaging=Packed in the best possible way, salePrice=100) ProductDto(id=2, name=Carrots, 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 on the window display the price for which you bought it in bulk and some other id. We modify 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 ultimately look 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()
            ));
        }
    }
}
Let's launch: Store window Buy: Potatoes, price: 100 Buy: Carrots, price: 70 Another thing! Now we think that dto brought good things, except for a bunch of additional code: 1. We can perform business logic without changing objects in the database (let’s say, well, we don’t need to have fields about packaging and sales price in this table). Potatoes will last well in storage even without packaging with a price tag, they are even superfluous there. 2. In this line List<ProductDto> productDtos = productService.findAll() we created a cache of objects that are convenient to work with within the business logic. This is if we put some of the goods in the back room of the store. 3. This allowed us to perform two business actions: packaging and markup, but we made a request to the database only once (queries to the database are quite difficult in terms of performance). You can package the product, put a price tag on it and put it on display - gradually picking it up from the utility room, rather than running after it every time to the storage room. To the question: “Why is it so difficult?”, people also try to find the answer, read . Next article
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION