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

2023-12-23

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

我看过this https://stackoverflow.com/questions/45814147/how-to-map-nested-collections-using-mapstruct, this https://github.com/mapstruct/mapstruct/issues/366 and this https://stackoverflow.com/questions/46310281/design-of-multiple-child-dtos-into-a-single-request-spring-boot得到一些想法但没有运气

Entity

@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

@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

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

Edited

将产品实体映射到产品 dto 后,我得到以下结果。它不绑定数据,也不包含其子属性。是否有上述情况mapper部分正确吗?

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

但结果应该如下所示:

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

TL;DR

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

为什么它不起作用

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

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

使用访客模式

因为我们需要选择正确的映射器manually,我们可以选择哪种方式cleaner或者更易于维护。这绝对是有主见的。

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

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

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 https://github.com/tpapad28/mapstruct-mapping-visitor为了简单起见,使用 MapStruct 的工厂。您只需使用 CDI 注入映射器即可。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 Spring Boot 中使用 Mapstruct 映射父级和子级? 的相关文章

随机推荐

  • 如何在 NetBeans 的 Android 项目中使用外部 jar?

    我需要创建一个 Android 库 我可以将其作为 jar 包含在任何 Android 应用程序中 我使用 NetBeans 6 8 nbandroid 插件和 Android SDK 到目前为止我采取的步骤是 1 创建库项目 其中包含 a
  • 用于 mvc 的 html 编辑器的数据属性

    编辑器的数据属性不起作用 Html EditorFor model gt model SireTag new data typeahead dsfsdfsd 当我打开 Chrome 浏览器时 我看不到文本框的任何数据属性 我尝试凝视并没有发
  • 为什么我可以将并行流收集到任意大的数组,但不能收集顺序流?

    从回答这个问题 https stackoverflow com q 49760006 7294647 我遇到了一个奇怪的功能 以下代码按照我的预期工作 现有数组中的前两个值将被覆盖 Integer newArray Stream of 7
  • 我应该将 firebase api 密钥隐藏到后端吗?不是因为数据安全,而是项目克隆问题

    有人可以帮我解答 firebase 安全问题吗 这就是我试图弄清楚的 我知道要让客户端与我的 firebase 应用程序交互 需要配置和firebase initializeApp config 将公开 每个使用客户端的人都可以在浏览器开发
  • 在 JavaScript 中检查字符串相等性的正确方法是什么?

    在 JavaScript 中检查字符串之间是否相等的正确方法是什么 always 直到您完全理解使用的差异和含义 and 运营商 使用 运算符 因为它可以让您避免出现晦涩 不明显 的错误和WTF 常规 由于内部类型强制 运算符可能会产生非常
  • 实体框架加载具有排序顺序的子集合

    我有两张表 一张是父表 一张是子表 子表有一个列排序顺序 数值 由于 EF 缺少支持在不公开排序顺序的情况下保留包含排序顺序的 IList 请参阅 实体框架持久保留子集合排序顺序 https stackoverflow com q 4327
  • 在 API 中使用 Facebook 身份验证令牌

    我有一个 iOS 应用程序 用户在设备上通过 Facebook 进行身份验证 并与我的服务器 API 进行通信 验证 今天 我将发送 Facebook 用户访问令牌 通过 SSL 服务器会使用 Facebook 验证该令牌 这就是 API
  • 如何以编程方式触发 D3 拖动事件?

    所以我有一些与拖动事件侦听器绑定的数据 myNodes enter append svg g call d3 behavior drag on drag function console log d3 event dx d3 event d
  • 根据文件是否包含字符串进行搜索然后删除

    我想在单个目录中的多个文本文件中搜索字符串 monkey 如果该字符串存在 那么要么 取决于最简单的 重命名匹配的字符串 例如更改猴子monkey1并保存然后文件并进行搜索 处理 or 删除任何具有匹配字符串的文件 已经搜索过 但似乎找不到
  • 重定向 301 中的重定向过多

    我想从旧网址到新网址进行 301 重定向 旧网址 php zend framework captcha codigo anti spam zend framework 新网址 http www demo31 com blog php zen
  • 如何防止 Emacs org-mode 分割窗口?

    我是一个新的 emacs 用户 使用 emacs 来实现很棒的组织模式 我的页面顶部有指向所有组织文件的链接 但每次单击链接时 它都会分割我的窗口 因此我只有一半的屏幕空间可用 如何设置它以便 emacs 不会水平分割窗口 而是为我的链接打
  • 如何在swift 4中获取今天和明天的日期

    如何获取当前日期unix epoch timeIntervalSince1970打印当前时间 有什么办法可以获取今天中午 12 点的时间吗 例如 当前时间为 2018 年 1 月 7 日下午 5 30 timeIntervalSince19
  • 收到 pylint 警告:“未找到配置文件,使用默认配置”

    pylint reports n main py Output No config file found using default configuration 您将得到 No config file found using default
  • 如何将 foreach 与二维对象数组一起使用?

    这是我的尝试 但不起作用 我是初学者 这个想法是有一个简单的 Kid years 整数二维数组来了解如何将 foreach 与对象一起使用 using System namespace Test class Kid public int y
  • 禁用反应式 Elasticsearch 客户端

    我的 spring boot 版本 2 4 1 应用程序已使用自动连接的 org elasticsearch client RestHighLevelClient 成功连接到 ElasticSearch v7 9 3 实例 我只需指定应用程
  • ASM x64 scanf printf 双精度,GAS

    我不明白为什么这段代码对我不起作用 我需要对双精度使用 scanf 函数 然后对同一个双精度使用 printf 使用此代码时结果并不好 我看到的都是非常随机的角色 data d1 double format asciz lf n forma
  • 检测PHP是否安装了Mod_Security?

    有没有简单的方法可以仅使用 PHP 来检测 modsecurity 是否已安装并启用 理想情况下 无需执行任何 exec 终端类型命令 有些人建议使用 apache get modules 但这个特定的网络主机不允许它显示 其他用户也提到了
  • 如何获得 R 中前 n 个值及其索引?

    我有一个只有一列的数据框 我想用它的索引找到最大的三个值 例如我的数据框df好像 distance 1 1 2 4 3 2 4 3 5 4 6 5 7 5 我想找到最大的 3 个值及其索引 所以我的预期结果是 distance 6 5 7
  • 如何在本地调试EventHubTrigger?

    我正在尝试在本地调试 Azure 函数 这是一个EventHubTrigger 问题是我需要在本地调试代码 因为我仍然没有 真实 设置 我的代码目前如下所示 public static class Notificator FunctionN
  • 如何在 Spring Boot 中使用 Mapstruct 映射父级和子级?

    我有父级 产品 和子级 书籍 家具 并且希望将产品实体映射到产品 DTO 如您所见 产品被映射并存储在数据库中的单个表中 如何映射具有子项额外详细信息的父项产品 我看过this https stackoverflow com questio