Spring Boot'ta Mapstruct kullanarak ebeveyn ve çocukları nasıl eşleştirebilirim?
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
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.
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.