如何在Spring Boot中使用Mapstruct映射父母和孩子?

丹尼尔

我有父母(产品)和孩子(书,家具),并想将产品实体映射到产品DTO。如您所见,产品已映射并存储在数据库的单个表中。如何映射具有其子级额外详细信息的父产品?

我看着这个这个这个有一些想法,但没有运气

实体

@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;
   ... 
}

映射器

@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);
}

服务

@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;
    }
}

已编辑

将产品实体映射到产品dto后,得到以下结果。它不绑定数据,也不包括其子级属性。上面的映射器部分正确吗?

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

但是结果应该像下面这样:

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

TL; DR

没有干净的方法可以做到这一点。原因在于Java的编译时方法选择。但是有一种使用访客模式的干净方法。

为什么不起作用

在迭代包含不同类型(产品,书籍,家具)的实体列表时,需要为每种类型调用不同的映射方法(即,不同的MapStruct映射器)。

除非您instanceof按照Amir的建议进行操作,并明确选择映射器,否则您需要使用方法重载来为每个实体类调用不同的映射方法。问题是Java将在编译时选择重载的方法,此时,编译器仅看到Product对象列表(存储库方法返回对象)。MapStruct或Spring或您自己的自定义代码尝试执行此操作并不重要。这也是为什么ProductMapper总是调用your的原因:它是在编译时可见的唯一类型。

使用访客模式

由于我们需要手动选择正确的映射器,因此我们可以选择哪种方法更清洁或更可维护。这绝对是有根据的。

我的建议是按以下方式使用访客模式(实际上是它的一种变体):

为需要映射的实体引入一个新接口:

public interface MappableEntity {

    public ProductDto map(EntityMapper mapper);
}

您的实体将需要实现此接口,例如:

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是访问者接口:

public interface EntityMapper {

    ProductDto map(Product entity);

    BookDto map(Book entity);

    FurnitureDto map(Furniture entity);

    // Add your next entity here
}

最后,您需要MasterMapper:

// 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

}

您的服务方法将如下所示:

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()调用:由于该问题与Spring无关,因此我为简化起见GitHub上使用MapStruct的工厂创建了一个工作示例您只需使用CDI注入映射器即可。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

我如何使用班级的父母和孩子?

如何在 MapStruct 映射器中使用 @ConfigProperty?

如何在Spring Boot中使用KeyHolder?

如何在Spring Boot中使用CommonsMultipartResolver

如何在Spring Boot中使用@NotNull?

如何在Spring Boot中使用CommandLineRunner?

如何在Spring Boot中使用ORDER BY?

如何使用Mapstruct和Kotlin在另一个映射器中使用映射器?

如何在Selenium JavaScript中使用最后一个孩子作为父母?

如何在Spring Boot中使用内部类并将其映射为列字段变量?

父母与同类型的孩子-如何映射?

如何在Spring Boot和Spring WebFlux中使用“功能bean定义Kotlin DSL”?

如何在 Spring Boot 和 Spring Data 中使用两个 Cassandra 数据源?

如何使用jQuery使孩子成为父母的父母?

如何在Spring Kafka和Spring Integration中使用GenericMessageJacksonDeserializer?

你如何在laravel eloquent中查询孩子和父母

如何在父母和孩子之间共享过滤器

如何在primeng中禁用NodeTree(父母和孩子)?

如何在Spring Boot + Mybatis中使用连接池

如何在 spring-boot 中使用 ElasticsearchRestClientAutoConfiguration?

如何在Spring Boot中使用EntityManager处理异常

如何在Spring Boot中使用Thymeleaf生成XML?

如何在Spring Boot中使用@ConfigurationProperties模拟类

在Spring Boot中如何在JPA中使用Criteria?

如何在Spring Boot中使用JPA的entityManager.getReference?

如何在Spring Boot中使用推土机?

如何在Spring Boot中使用Thymeleaf保存角色?

如何在Spring Boot Controller中使用Jquery AJAX

如何在Spring Boot中使用阻塞队列?