Data Transfer Object (Объект передачи данных)
Объект, который пересылает данные между процессами для уменьшения количества вызовов методов.
При работе с удалённым интерфейсом, таким как, например, Remote Facade, каждый запрос к нему достаточно затратен. В результате, приходится уменьшать количество вызовов, что означает необходимость передачи большего количества данных за один вызов. Чтобы реализовать это, как вариант, можно использовать множество параметров. Однако, при этом зачастую код получается неуклюжим и неудобным. Также это часто невозможно в таких языках, как Java, которые возвращают лишь одно значение.
Решением здесь является паттерн Data Transfer Object , который может хранить всю необходимую для вызова информацию. Он должен быть сериализуемым для удобной передачи по сети. Обычно используется объект-сборщик для передачи данных между DTO и объектами в приложении.
В сообществе Sun многие используют термин «Value Object» для обозначения этого паттерна. Мартин Фаулер подразумевает под этим термином ( Value Object ) несколько иной паттерн. Обсуждение этого можно прочесть в его книге P of EEA на странице 487.
Использована иллюстрация с сайта Мартина Фаулера.
- Главная
- Список паттернов
- Сайт создан и поддерживается Василием Кулаковым.
Что такое DTO в Java?
Допустим вы пишите игру шашки. И у вас есть класс Checker. У этого класса будут только поля(цвет, дамка, и т.п.) и геттеры/сеттеры. Вот вам и DTO. Ну и конструктор.
12 дек 2018 в 7:20
Тогда какой смысл в слове Transfer? Он же должен передавать данные по объектам? Как это написано тут DTO
12 дек 2018 в 7:20
@Teemitze DTO переводится как «объект, передающий данные». Данные, которые он передает — это и есть поля.
12 дек 2018 в 7:21
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
Объект Customer — DTO.
DTO объект — объект, который не содержит методы. Он может содержать только поля, геттеры/сеттеры, и конструкторы.
Data Transfer Object — объект, передающий данные. Данные — это и есть поля в классе.
Реальный пример — игра шашки. У вас должен быть объект Checker (шашка). У него не должно быть методов, только поля.
public class Checker < private COLOR checkerColor; private Coordinate coordinate; //show checker coordinate private boolean isQueen; //show is the checker queen public Checker(COLOR checkerColor, int xCoordinate, int yCoordinate) < this.checkerColor = checkerColor; coordinate = new Coordinate(xCoordinate, yCoordinate); isQueen = false; >public Checker() <> public COLOR getColor() < return checkerColor; >public Coordinate getCoordinate() < return coordinate; >public boolean isQueen() < return isQueen; >public void setCoordinate(Coordinate coordinate) < this.coordinate.setCoordinates(coordinate.getX(), coordinate.getY()); >public void setQueen() < isQueen = true; >>
Или класс Cell (шашечное поле).
public class Cell < private boolean isBusy; //shows does the field is occupied with the checker private Coordinate coordinate; //show field coordinate public Cell(boolean isBusy, int x, int y) < coordinate = new Coordinate(x, y); this.isBusy = isBusy; >public Cell() <> public boolean isBusy() < return isBusy; >public void setBusy(boolean isBusy) < this.isBusy = isBusy; >public Coordinate getCoordinate() < return coordinate; >>
Или класс Board (доска):
public class Board < private Listcells; //list with 64 Fields() private List checkers; //list with Checkers(), whose number falls from 24 to 0 public Board() < cells = new LinkedList<>(); checkers = new LinkedList<>(); > public List getCells() < return cells; >public List getCheckers() < return checkers; >>
Или класс Coordinate . Хотя у него есть методы(переопределенный equals и compare ), но это методы из Object и он тоже может считаться DTO объектом, т.к. он сделан только для того, что бы хранить данные(координаты).
public class Coordinate < private int x; //x coordinate private int y; //y coordinate public Coordinate(int x, int y) < this.x = x; this.y = y; >public int getX() < return x; >public void setX(int x) < this.x = x; >public int getY() < return y; >public void setY(int y) < this.y = y; >public void setCoordinates(int x, int y) < this.x = x; this.y = y; >@Override public boolean equals(Object o) < if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Coordinate that = (Coordinate) o; return x == that.x && y == that.y; >public boolean compare(Coordinate that, int xMove, int yMove)
Spring — это не страшно, прослойка из DTO

СОДЕРЖАНИЕ ЦИКЛА СТАТЕЙ Продолжаем говорить про 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 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())
На выходе получим: Витрина магазина 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 list) < list.forEach(productDto ->productDto.setPackaging(packaging) ); > // делаем деньги public void makeMoney(List list) < list.forEach(productDto ->productDto.setSalePrice(productDto.getPurchasePrice() * margin) ); >
Возвращаемся в ItiiateUtil и выкладывание на витрину заменяем на следующий код
List productDtos = productService.findAll(); productService.pack(productDtos); productService.makeMoney(productDtos); System.out.println("\nВитрина магазина"); for (ProductDto dto : productDtos))
Выполняем: Витрина магазина ProductDto(id=1, name=Картофель, purchasePrice=20, packaging=Упаковано в лучшем виде, salePrice=100) ProductDto(id=2, name=Морковь, purchasePrice=14, packaging=Упаковано в лучшем виде, salePrice=70) Товар красиво упакован, есть цена, но вы где-нибудь видели, что бы на витрине писали цену за которую купили оптом и еще id какой-то. Дорабатываем напильником, написанный выше код:
List productDtos = productService.findAll(); productService.pack(productDtos); productService.makeMoney(productDtos); System.out.println("\nВитрина магазина"); for (ProductDto dto : productDtos)
class InitiateUtils в итоге должен быть таким:
@Service @RequiredArgsConstructor public class InitiateUtils implements CommandLineRunner < private final ProductService productService; @Override public void run(String. args) throws Exception < Listproducts = new ArrayList<>( Arrays.asList( new ProductEntity() .setName("Картофель") .setPurchasePrice(20), new ProductEntity() .setName("Морковь") .setPurchasePrice(14) )); productService.saveAll(products); List 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 productDtos = productService.findAll() мы создали кэш из объектов с которыми удобно работать в рамках бизнес-логики. Это, если бы мы положили часть товара в подсобку магазина. 3. Это нам позволило, совершить два бизнес действия: упаковка и наценка, но запрос в базу сделали только один раз (запросы в базу довольно тяжелы в плане производительности). Товар можно упаковывать, клеить ценник и выкладывать на витрину — постепенно набирая его из подсобки, а не бегать за ним каждый раз в хранилище. На вопрос: «Зачем так сложно?», люди тоже пытаются найти ответ, почитайте. Следующая статья
Различия между DTO, VO, POJO, JavaBeans
Разработка приложений на Java предполагает работу с различными типами объектов. Часто встречаются термины, такие как DTO, VO, POJO, JavaBeans. Однако для понимания их функций и контекстов использования требуются дополнительные разъяснения.
POJO (Plain Old Java Object)
POJO — это обычный Java-объект, который не следует никаким специфическим Java фреймворкам или интерфейсам. Он может содержать любое количество полей и методов, которые не обязательно должны быть приватными или публичными. POJO обычно используются для инкапсуляции бизнес-логики в приложениях.
Пример POJO может выглядеть следующим образом:
public class MyClass < private String name; private int age; public MyClass(String name, int age) < this.name = name; this.age = age; >public void sayHello() < System.out.println("Hello, " + name); >>
JavaBeans
JavaBeans — это специализированный тип POJO, который следует определенным правилам. В JavaBeans все поля должны быть приватными, а для доступа к ним используются getter и setter методы. Кроме того, JavaBeans должны иметь конструктор без параметров.
public class MyClass < private String name; private int age; public MyClass() < >public String getName() < return name; >public void setName(String name) < this.name = name; >public int getAge() < return age; >public void setAge(int age) < this.age = age; >>
DTO (Data Transfer Object)
DTO — это объект, который используется для передачи данных между подсистемами приложения. Он содержит только поля и getter/setter методы для доступа к ним. DTO не содержит бизнес-логики и используется только для передачи данных.
public class MyClassDTO < private String name; private int age; public String getName() < return name; >public void setName(String name) < this.name = name; >public int getAge() < return age; >public void setAge(int age) < this.age = age; >>
VO (Value Object)
VO — это объект, который используется для передачи значений между методами и объектами. Он аналогичен DTO, но в отличие от DTO, VO не должен быть изменяемым. Это значит, что после создания VO, его состояние не должно меняться.
public final class MyClassVO < private final String name; private final int age; public MyClassVO(String name, int age) < this.name = name; this.age = age; >public String getName() < return name; >public int getAge() < return age; >>
В целом, выбор между этими типами объектов зависит от конкретных требований к приложению и его архитектуре.
