Spring Boot'ta Mapstruct kullanarak ebeveyn ve çocukları nasıl eşleştirebilirim?

Dec 01 2020

Ebeveyn (ürün) ve çocuklarım (kitap, mobilya) var ve ürün varlığını ürün DTO ile eşlemek istiyorum. Gördüğünüz gibi ürün haritalandırılmış ve veri tabanında tek bir tabloda depolanmıştır. Alt öğesinin ekstra ayrıntılarını içeren ana ürünü nasıl eşleyebilirim?

Ben de göz bu , bu ve bu bazı fikir ama hayır şans almak için

Varlık

@Entity
@Table(name = "product")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Product {
  @Id
  private long id;
  private String productName;
}

@Entity
@DiscriminatorValue("Book")
public class Book extends Product { 
  private String author;
  ...
}
@Entity
@DiscriminatorValue("Furniture")
public class Furniture extends Product {
  String color;
  ...
}

DTO

public class ProductDto {
  private long id;
  private String productName;
  ...
}

public class BookDto extends ProductDto {
  private String author;
  ...
}
public class FurnitureDto extends ProductDto {
   String color;
   ... 
}

Haritacı

@Mapper(uses = {BookMapper.class,FurnitureMapper.class})
public interface ProductMapper {
    
    ProductDto productToProductDto(Product product);
    Product productDtoToProduct(ProductDto productDto);
}

@Mapper
public interface BookMapper {
    BookDto bookToBookDto(Book book);
    Book bookDtoToBook(BookDto bookDto);
}

@Mapper
public interface FurnitureMapper {
    FurnitureDto furnitureToFurnitureDto(Furniture furniture);
    Furniture furnitureDtoToFurniture(FurnitureDto furnitureDto);
}

Hizmet

@Service
public class ProductServiceImpl implements ProductService {

    @Autowired
    ProductRepository productRepository;
    @Autowired
    ProductMapper productMapper;

    @Override
    public List<ProductDto> getAllProducts() {
        List<ProductDto> listOfProducts = new ArrayList<>();
        productRepository.findAll().forEach(i -> 
        listOfProducts.add(productMapper.productToProductDto(i)));
        return listOfProducts;
    }
}

Düzenlendi

Ürün varlığını dto ürününe eşledikten sonra aşağıdaki sonucu alıyorum. Verileri bağlamaz ve alt özelliklerini içermez. Yukarıdaki eşleyici bölümü doğru mu?

[
    {
        "id": 0,
        "productName": null
    },
    {
        "id": 0,
        "productName": null
    },
    ...
]

Ancak sonuç aşağıdaki gibi olmalıdır:

[
    {
        "id": 11,
        "productName": ABC,
        "author":"James"
    },
    {
        "id": 22,
        "productName": XYZ,
        "color":"Oak"
    },
    ...
]

Yanıtlar

2 TasosP. Dec 10 2020 at 05:01

TL; DR

Bunu yapmanın temiz bir yolu yok. Bunun nedeni, Java'nın derleme zamanı yöntem seçiminde yatmaktadır. Ancak ziyaretçi modelini kullanmanın biraz temiz bir yolu var.

Neden çalışmıyor

Farklı türler (Ürün, Kitap, Mobilya) içeren varlıkların listesini yinelerken, her tür için farklı bir eşleme yöntemi (yani farklı bir MapStruct eşleştiricisi) çağırmanız gerekir.

instanceofAmir'in önerdiği gibi yola devam etmediğiniz ve eşleştiriciyi açıkça seçmediğiniz sürece , varlık sınıfı başına farklı eşleme yöntemlerini çağırmak için yöntem aşırı yüklemesini kullanmanız gerekir. Sorun, Java'nın derleme sırasında aşırı yüklenmiş yöntemi seçmesi ve bu noktada derleyicinin yalnızca Productnesnelerin bir listesini (depo yönteminiz tarafından döndürülenleri) görmesidir . MapStruct veya Spring veya kendi özel kodunuzun bunu yapmaya çalışması gerçekten önemli değil. Sizin ProductMapperher zaman çağrılmasının nedeni de budur : derleme zamanında görünen tek türdür.

Ziyaretçi desenini kullanma

Doğru eşleştiriciyi manuel olarak seçmemiz gerektiğinden, hangi yolun daha temiz veya daha bakımlı olduğunu seçebiliriz . Bu kesinlikle düşünülmüş.

Önerim, ziyaretçi modelini (aslında bir varyasyonu) aşağıdaki şekilde kullanmaktır:

Eşlenmesi gereken varlıklar için yeni bir arayüz tanıtın:

public interface MappableEntity {

    public ProductDto map(EntityMapper mapper);
}

Varlıklarınızın bu arayüzü uygulaması gerekecektir, örneğin:

public class Book extends Product implements MappableEntity {
//...
@Override
    public ProductDto map(EntityMapper mapper) {
        return mapper.map(this);//This is the magic part. We choose which method to call because the parameter is this, which is a Book!
    }
}

EntityMapper, ziyaretçi arayüzüdür:

public interface EntityMapper {

    ProductDto map(Product entity);

    BookDto map(Book entity);

    FurnitureDto map(Furniture entity);

    // Add your next entity here
}

Son olarak, MasterMapper'a ihtiyacınız var:

// Not a class name I'm proud of
public class MasterMapper implements EntityMapper {

    // Inject your mappers here

    @Override
    public ProductDto map(Product product) {
        ProductMapper productMapper = Mappers.getMapper(ProductMapper.class);
        return productMapper.map(product);
    }

    @Override
    public BookDto map(Book product) {
        BookMapper productMapper = Mappers.getMapper(BookMapper.class);
        return productMapper.map(product);
    }

    @Override
    public FurnitureDto map(Furniture product) {
        FurnitureMapper productMapper = Mappers.getMapper(FurnitureMapper.class);
        return productMapper.map(product);
    }

    // Add your next mapper here

}

Hizmet yönteminiz şöyle görünecektir:

MasterMapper mm = new MasterMapper();
List<Product> listOfEntities = productRepository.findAll();
List<ProductDto> listOfProducts = new ArrayList<>(listOfEntities.size());
listOfEntities.forEach(i -> {
        if (i instanceof MappableEntity) {
            MappableEntity me = i;
            ProductDto dto = me.map(mm);
            listOfProducts.add(dto);
        } else {
            // Throw an AssertionError during development (who would run server VMs with -ea ?!?!)
            assert false : "Can't properly map " + i.getClass() + " as it's not implementing MappableEntity";
            // Use default mapper as a fallback
            final ProductDto defaultDto = Mappers.getMapper(ProductMapper.class).map(i);
            listOfProducts.add(defaultDto);
        }
     });
    return listOfProducts;

Mappers.getMapper()Aramaları güvenle göz ardı edebilirsiniz : sorun Spring ile ilgili olmadığından, basitlik için MapStruct'ın fabrikalarını kullanarak GitHub'da çalışan bir örnek oluşturdum . Haritacılarınızı CDI kullanarak enjekte edeceksiniz.

1 AmirSchnell Dec 09 2020 at 16:44

Bu, bu sayıda açıklanan senaryo ile tamamen aynıdır .
Ne yazık ki bu sorun şu anda hala açık ve şu ana kadar kimse çözüm sağlamadı. Yani bence istediğin şey mümkün değil.

Sorununuzu çözmenin tek yolu, hizmetinizdeki kodu değiştirmektir.

O konuda da belirtildiği gibi yapabileceğiniz şeylerden biri, her Productnesneyi aşağıdakilerle test etmektir instanceof:

@Autowired
BookMapper bookMapper;
@Autowired
FurnitureMapper furnitureMapper;

public List<ProductDto> getAllProducts() {
    List<ProductDto> listOfProducts = new ArrayList<>();
    List<Product> all = productRepository.findAll();
    for(Product product : all) {
        if(product instanceof Book) {
            listOfProducts.add(bookMapper.bookToBookDto((Book)product));
        } else if(product instanceof Furniture) {
            listOfProducts.add(furnitureMapper.furnitureToFurnitureDto((Furniture)product));
        }
    }
    return listOfProducts;
}

Ayrıca ProductMapperuzatabilirsiniz BookMapperve FurnitureMapperböylece bunları enjekte etmek zorunda kalmazsınız.