Java >> Java Tutorial >  >> Java

Konvertierung der Entität in DTO mit ModelMapper

In diesem Beitrag werde ich zeigen, wie wir die Konvertierung von Entitäten in DTO mithilfe der ModelMapper-Bibliothek erreichen können . Wir werden im Grunde eine einfache REST-API für orders erstellen während die Transformation von Entity zu DTO und umgekehrt gezeigt wird.

Unternehmensarchitektur verstehen

In den meisten Unternehmensarchitekturen gibt es REST-APIs. Ein Konsument dieser APIs sendet eine Anfrage und der Server antwortet mit einer Antwort. Die Transformation von Anfrage zu Antwort findet hinter der API statt. Sie führen Geschäftslogik aus und ändern diese Objekte.

Traditionell gibt es drei Schichten in der Architektur. Webschicht, Geschäftsschicht und Datenbankschicht.

Ihr Objekt in der Datenbankebene unterscheidet sich also vollständig von demselben Objekt in der Webebene. Datenbankentitäten aus der Datenbankschicht enthalten bestimmte Felder, die Sie in der Webschicht nicht benötigen. Darüber hinaus sollte jedes Objekt aus der Webschicht benutzerfreundlich sein. Benutzer müssen nicht raten, womit sie es zu tun haben. Es sollte selbsterklärend sein. Dies wird deutlicher, wenn ich die Implementierung davon zeige.

Schichtentrennung zwischen Entität und DTO

Data Transfer Objects (DTO) sind die Objekte, die von einer Schicht zur anderen verschoben werden. Diese Objekte sind benutzerfreundlicher und enthalten nur die wichtigsten Felder.

Andererseits repräsentieren Datenbankentitäten Datenbanktabellen. Viele automatisch generierte Felder können für Benutzer unnötig sein. Trotzdem sind sie Teil von Datenbankentitäten. In DTO ignorieren wir diese Felder. Da diese Felder automatisch generiert werden, kann unser Datenbankschichtcode damit umgehen.

Wenn das Objekt jedoch von der Webschicht zur Datenbankschicht wandert, muss es transformiert werden, damit es von dieser Schicht verwendet werden kann. Im nächsten Abschnitt werde ich zeigen, wie wir diese Konvertierung von Entity zu DTO mit der ModelMapper-Bibliothek erreichen können.

Die Entität für DTO mit ModelMapper

Die ModelMapper-Bibliothek bietet eine einfachere Möglichkeit zum Konvertieren eines Entitätsobjekts in ein DTO und umgekehrt.

In dieser Demo habe ich ein Szenario, in dem ein Kunde einen Artikel bestellt. Eine Bestellung für den Artikel wird erstellt. Wir speichern Bestelldaten, Kundendaten und die Adresse des Kunden.

Um diese Bibliothek in unserer Anwendung verwenden zu können, fügen Sie die Abhängigkeit wie folgt hinzu:

implementation 'org.modelmapper:modelmapper:2.3.0'

Auch wenn wir ModelMapper-Bibliotheksfunktionen verwenden möchten, fügen wir eine Bean für dieselbe wie folgt hinzu:

        @Bean
	public ModelMapper modelMapper()
	{
		return new ModelMapper();
	}

Zuvor habe ich angegeben, dass ein Kunde bestellen kann. Also werden wir dies implementieren, indem wir eine REST-API haben, die Bestelldetails und Kundendetails erstellt.

Domänenebene

In dieser Architektur haben wir Bestellungen, die Kunden an bestimmte Adressen bestellen.

In einem Datenbankentitätsdiagramm sieht es wie folgt aus:

Ein Kunde kann mehrere Artikel bestellen, also Mehrfachbestellungen. Mehrere Bestellungen können an eine einzige Adresse gehen.

Unsere Domain-Objekte sehen wie folgt aus, beginnend mit Order:


package com.betterjavacode.modelmapperdemo.models;

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

@Entity(name = "Order")
@Table(name = "orders")
public class Order implements Serializable
{
    private static final long serialVersionUID = 7385741327704693623L;

    public Order()
    {

    }

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

    @Column(name ="order_item")
    private String orderItem;

    @Column(name = "description")
    private String description;


    @ManyToOne
    @JoinColumn(name = "customer_id")
    private Customer customer;


    @ManyToOne
    @JoinColumn(name = "address_id")
    private Address address;
    
    // Getters and setters omitted for demo purposes


}

Die Adresse lautet:


package com.betterjavacode.modelmapperdemo.models;

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Entity(name = "Address")
@Table(name = "address")
public class Address implements Serializable
{
    private static final long serialVersionUID = -439961851267007148L;

    public Address()
    {

    }

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

    @Column
    private String street;

    @Column
    private String city;

    @Column
    private String state;

    @Column
    private String country;

    @Column
    private int zipcode;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List orderList = new ArrayList<>();


}

Und Kunde wird sein:


package com.betterjavacode.modelmapperdemo.models;

import javax.persistence.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

@Entity(name = "Customer")
@Table(name = "customer")
public class Customer implements Serializable
{
    private static final long serialVersionUID = -2205735699915701334L;

    public Customer()
    {

    }

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

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column
    private String email;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List orderList = new ArrayList<>();


}

Diese drei Objekte Bestellung, Kunde und Adresse repräsentieren unsere Datenbankentitäten und sind Teil der Datenbankschicht. Der Rest der Persistenz ist mit Repositories unkompliziert.

Webschicht

Die Webschicht konzentriert sich hauptsächlich auf die Controller, die wir für unsere APIs erstellen. Diese Controller sind dafür verantwortlich, die Anfrage vom Client zu erhalten. Außerdem werden die Objekte, die wir über APIs verfügbar machen, DTO-Objekte sein. Dieses DTO-Objekt für Order wird wie folgt aussehen:


package com.betterjavacode.modelmapperdemo.dtos;

public class OrderDTO
{
    String orderItem;
    String orderDescription;
    String customerFirstName;
    String customerLastName;
    String customerEmail;
    String streetAddress;
    String cityAddress;
    String stateAddress;
    String countryAddress;
    int zipcodeAddress;

   // Getters and Setters omitted for demo

}

Dieses DTO-Objekt enthält Felder aus Auftrag, Kunde und Adresse. Unsere API empfängt dieses Objekt in einer POST-Anforderung, wir wandeln dieses DTO-Objekt mithilfe der ModelMapper-Bibliothek in ein Entitätsobjekt um und übergeben dieses Entitätsobjekt dann an unseren Service Klasse weiter zu verarbeiten.

OrderController wird wie folgt aussehen:


package com.betterjavacode.modelmapperdemo.controllers;

import com.betterjavacode.modelmapperdemo.dtos.OrderDTO;
import com.betterjavacode.modelmapperdemo.models.Order;
import com.betterjavacode.modelmapperdemo.service.IOrderService;
import org.modelmapper.ModelMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/v1/betterjavacode/orders")
public class OrderController
{
    private static final Logger LOGGER = LoggerFactory.getLogger(OrderController.class);

    @Autowired
    private IOrderService orderService;

    @Autowired
    private ModelMapper modelMapper;

    @PostMapping
    public OrderDTO createOrder(@RequestBody OrderDTO orderDTO)
    {
        Order order = convertToEntity(orderDTO);
        Order orderCreated = orderService.createOrder(order);

        return convertToDTO(orderCreated);
    }

    @GetMapping("/{customerId}")
    public List getAllOrders(@PathVariable("customerId") long customerId)
    {
        List orderList = orderService.getAllOrdersForCustomer(customerId);
        List orderDTOs = new ArrayList<>();
        for(Order order : orderList)
        {
            orderDTOs.add(convertToDTO(order));
        }
        return orderDTOs;
    }


    private Order convertToEntity (OrderDTO orderDTO)
    {
        LOGGER.info("DTO Object = {} ", orderDTO);

        Order order = modelMapper.map(orderDTO, Order.class);

        return order;
    }

    private OrderDTO convertToDTO (Order order)
    {
        OrderDTO orderDTO = modelMapper.map(order, OrderDTO.class);
        return orderDTO;
    }
}

Wir haben eine POST-API zum Erstellen von Bestellungen und eine GET-API zum Abrufen von Bestellungen für einen Kunden.

ModelMapper-Bibliothek

In unserem Controller verwenden wir ModelMapper Bean, um ein DTO-Objekt in eine Entität und ein Entitätsobjekt in eine DTO umzuwandeln.

Wie erreicht die ModelMapper-Bibliothek das eigentlich?

Wenn ein Mapper die map-Methode aufruft, analysiert er die Quell- und Zieltypen, um zu bestimmen, welche Eigenschaften abgeglichen werden müssen. Es verwendet eine passende Strategie und Konfiguration, um diese Eigenschaften abzubilden. Sobald die Eigenschaften zugeordnet sind, werden die Daten zugeordnet.

Wenn wir uns also unsere DTO-Klasse ansehen, haben wir Eigenschaften wie customerFirstName , customerLastName die mit Customer übereinstimmen Entitätsobjekt, während Eigenschaften wie streetAddress , cityAddress stimmt mit den Eigenschaften von Address überein Objekt.

ModelMapper bietet auch eine Möglichkeit, die Eigenschaften explizit zuzuordnen, wenn Sie sich dafür entscheiden.


modelMapper.typeMap(Order.class, OrderDTO.class).addMappings(mapper -> {
  mapper.map(src -> src.getBillingAddress().getStreet(),
      Destination::setBillingStreet);
  mapper.map(src -> src.getBillingAddress().getCity(),
      Destination::setBillingCity);
});

Die Bibliothek bietet drei Arten von Matching-Strategien:

  1. Standard – Bei dieser Strategie gleicht die Bibliothek die Quelleigenschaften intelligent mit den Zieleigenschaften ab. Diese Strategie ist standardmäßig konfiguriert. Alle Ziel-Eigenschaftsnamen-Token müssen übereinstimmen.
  2. Los – Eigenschaften von Quelle und Ziel sind lose aufeinander abgestimmt. Wenn die Eigenschaftshierarchien von Quell- und Zielobjekten unterschiedlich sind, kann die lose Strategie funktionieren. Für den letzten Ziel-Property-Namen müssen alle Token übereinstimmen.
  3. Streng – Quelleigenschaften sollten genau mit Zieleigenschaften übereinstimmen. Token stimmen in einer strikten Reihenfolge überein. Diese Strategie lässt keine Zweideutigkeit zu.

Eine vollständige Demo von Entity to DTO mit ModelMapper

Wir haben unsere REST-Controller- und Domain-Objekte gezeigt. Jetzt werde ich zeigen, wie wir Postman verwenden können, um diese REST-API aufzurufen, indem wir ein DTO-Objekt an die POST-API übergeben.

Wir erstellen eine Bestellung eines Artikels, den ein Kunde bestellt.

In der Anfrage habe ich ein DTO-Objekt übergeben, das Informationen zu Bestellung, Kunde und Adresse enthält.

In unserer Serviceschicht verarbeiten wir konvertierte Entitätsobjekte, validieren Geschäftsregeln und speichern diese Informationen, um die Bestellung zu erstellen.

Vermeiden von technischen Schulden

Es ist wichtig, das Konzept von DTO- und Entitätsobjekten zu verstehen. Wann Sie welche Art von Objekt verwenden, um technische Schulden zu vermeiden. Aus persönlicher Erfahrung habe ich gesehen, dass viele Junior-Entwickler den Fehler gemacht haben, Entitätsobjekte in einem Web-Layer zu verwenden. Abhängig von Ihrer Anwendung kann dies die Komplexität des Systems erhöhen.

Schlussfolgerung

In diesem Beitrag habe ich gezeigt, wie wir Entitäten mithilfe der Modelmapper-Bibliothek in DTO konvertieren können. Sie können die Modelmapper-Bibliothek hier herunterladen. Der Code für diese Demo ist in meinem GitLab-Repository verfügbar. Wenn Ihnen dieser Beitrag gefallen hat, können Sie meinen Blog hier abonnieren.

Referenzen

  1. Model-Mapper-Bibliothek – ModelMapper

Java-Tag