【问题标题】:How to map parent and children using Mapstruct in Spring Boot?如何在 Spring Boot 中使用 Mapstruct 映射父母和孩子?
【发布时间】:2020-12-10 06:06:28
【问题描述】:

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

我查看了 thisthisthis 以了解一些想法,但没有运气

实体

@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 后,我得到以下结果。它不绑定数据,也不包括其子属性。上面的 ma​​pper 部分是否正确?

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

然而结果应该如下所示:

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

【问题讨论】:

  • 您面临的具体问题是什么?
  • @NikolaiShevchenko 我已经用我得到的错误更新了问题
  • 因为错误消息指出问题不在于 MapStruct,而在于 Jackson
  • @NikolaiShevchenko 之前的错误是注释。我已经更新了这个问题。似乎数据未正确绑定,因为映射后我得到空数据。你能检查一下我为父类和子类编写映射器的映射器部分吗?
  • 检查this。可能有用!

标签: java spring-boot mapstruct


【解决方案1】:

TL;DR

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

为什么它不起作用

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

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

使用访问者模式

由于我们需要手动选择正确的映射器,我们可以选择更清洁或更易于维护的方式。这绝对是有意见的。

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

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

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 无关,为了简单起见,我使用 MapStruct 的工厂在 GitHub 上创建了一个工作示例。您只需使用 CDI 注入映射器。

【讨论】:

    【解决方案2】:

    这与this issue 中描述的情况完全相同。
    不幸的是,这个问题目前仍然存在,到目前为止没有人提供解决方案。所以我认为你想要的是不可能的。

    解决问题的唯一方法是更改​​服务中的代码。

    正如问题中提到的,您可以做的事情是用instanceof 测试每个Product 对象:

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

    你也可以让你的 ProductMapper 扩展 BookMapperFurnitureMapper,这样你就不必注入它们了。

    【讨论】:

      猜你喜欢
      • 2014-08-22
      • 2021-11-19
      • 1970-01-01
      • 1970-01-01
      • 2021-02-24
      • 1970-01-01
      • 2020-10-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多