当前位置: 首页 > 知识库问答 >
问题:

如何在考虑可伸缩性的同时正确地将域实体转换为DTO

邵献
2023-03-14

我已经阅读了几篇关于将域对象转换为DTO的文章和Stackoverflow文章,并在我的代码中试用了它们。说到测试和可伸缩性,我总是面临一些问题。我知道以下三种将域对象转换为DTO的可能解决方案。大部分时间我都在使用Spring。

解决方案1:服务层中用于转换的私有方法

第一个可能的解决方案是在服务层代码中创建一个小的“助手”方法,将检索到的数据库对象转换为我的DTO对象。

@Service
public MyEntityService {

  public SomeDto getEntityById(Long id){
    SomeEntity dbResult = someDao.findById(id);
    SomeDto dtoResult = convert(dbResult);
    // ... more logic happens
    return dtoResult;
  }

  public SomeDto convert(SomeEntity entity){
   //... Object creation and using getter/setter for converting
  }
}

赞成的意见:

  • 易于实施

缺点:

  • 测试时出现问题,因为私有方法中使用了new SomeEntity(),如果对象嵌套很深,我必须提供我的when(someDao.findById(id))的适当结果。然后返回(alsodeplynestedobject)以避免在转换同时溶解嵌套结构时出现空指针

解决方案2:DTO中的附加构造函数,用于将域实体转换为DTO

我的第二个解决方案是向我的DTO实体添加一个额外的构造函数来转换构造函数中的对象。

public class SomeDto {

 // ... some attributes

 public SomeDto(SomeEntity entity) {
  this.attribute = entity.getAttribute();
  // ... nesting convertion & convertion of lists and arrays
 }

}

赞成的意见:

  • 不需要额外的转换类

缺点:

  • 在服务代码中使用new SomDto(),因此我必须提供正确的嵌套对象结构,这是我的某物嘲笑的结果。

解决方案3:使用Spring的转换器或任何其他外部化Bean进行此转换

如果最近看到Spring为转换原因提供了一个类:Converter

赞成的意见:

  • 易于测试,因为我可以在测试用例期间模拟结果
  • 任务分离-

缺点:

  • 不会随着我的域模型的增长而“扩展”。对于很多实体,我必须为每个新实体创建两个转换器(-

你对我的问题有更多的解决方案吗?你是如何处理的?您是否为每个新的域对象创建了一个新的转换器,并且可以与项目中的类数量“共存”?

提前谢谢!


共有3个答案

佴英奕
2023-03-14

我最终没有使用一些神奇的映射库或外部转换器类,只是添加了一个我自己的小bean,它有convert方法从每个实体到我需要的每个数据。原因是映射是:

或者简单得愚蠢,我只是从一个字段复制一些值到另一个字段,也许用一个小实用程序方法,

或者是相当复杂的,与仅仅写出代码相比,在自定义参数中写入某些通用映射库要复杂得多。例如,客户机可以发送JSON,但在后台会将其转换为实体,当客户机再次检索这些实体的父html" target="_blank">对象时,会将其转换回JSON。

这意味着我可以直接调用。映射(converter::convert)任何实体集合,以获取我的DTO流。

将所有内容都放在一个类中是可伸缩的吗?即使使用通用映射器,此映射的自定义配置也必须存储在某个位置。除了少数情况外,代码通常非常简单,所以我不太担心这个类的复杂性激增。我也不希望有几十个实体,但如果我有,我可能会在每个子域的一个类中分组这些转换器。

在实体和DTO中添加一个基类,这样我就可以编写一个通用的转换器接口,并在每个类中实现它,这是不需要的(现在还需要吗?)我也是。

汪胤
2023-03-14

我喜欢公认答案中的第三个解决方案。

解决方案3:使用Spring的转换器或任何其他外部化Bean进行此转换

我用以下方式创建DtoConverter:基本实体类标记:

public abstract class BaseEntity implements Serializable {
}

抽象数据到类标记:

public class AbstractDto {
}

通用转换器接口:

public interface GenericConverter<D extends AbstractDto, E extends BaseEntity> {

    E createFrom(D dto);

    D createFrom(E entity);

    E updateEntity(E entity, D dto);

    default List<D> createFromEntities(final Collection<E> entities) {
        return entities.stream()
                .map(this::createFrom)
                .collect(Collectors.toList());
    }

    default List<E> createFromDtos(final Collection<D> dtos) {
        return dtos.stream()
                .map(this::createFrom)
                .collect(Collectors.toList());
    }

}

转换器接口:

public interface CommentConverter extends GenericConverter<CommentDto, CommentEntity> {
}

CommentConverter类实现:

@Component
public class CommentConverterImpl implements CommentConverter {

    @Override
    public CommentEntity createFrom(CommentDto dto) {
        CommentEntity entity = new CommentEntity();
        updateEntity(entity, dto);
        return entity;
    }

    @Override
    public CommentDto createFrom(CommentEntity entity) {
        CommentDto dto = new CommentDto();
        if (entity != null) {
            dto.setAuthor(entity.getAuthor());
            dto.setCommentId(entity.getCommentId());
            dto.setCommentData(entity.getCommentData());
            dto.setCommentDate(entity.getCommentDate());
            dto.setNew(entity.getNew());
        }
        return dto;
    }

    @Override
    public CommentEntity updateEntity(CommentEntity entity, CommentDto dto) {
        if (entity != null && dto != null) {
            entity.setCommentData(dto.getCommentData());
            entity.setAuthor(dto.getAuthor());
        }
        return entity;
    }

}
锺离穆冉
2023-03-14

解决方案1:服务层中用于转换的私有方法

我想解决方案1不会很好地工作,因为您的DTO是面向域的,而不是面向服务的。因此,它们很可能用于不同的服务中。因此,映射方法不属于一个服务,因此不应在一个服务中实现。您将如何在另一个服务中重用映射方法?

如果您对每个服务方法使用专用的DTO,解决方案将运行良好。但在最后更多关于这一点。

解决方案2:DTO中的附加构造函数,用于将域实体转换为DTO

一般来说,这是一个不错的选择,因为您可以将DTO视为实体的适配器。换句话说:DTO是一个实体的另一种表示。这种设计通常会包装源对象,并提供一些方法,为您提供包装对象的另一个视图。

但DTO是一个数据传输对象,因此它可能早晚被序列化并通过网络发送,例如使用spring的远程处理功能。在这种情况下,接收此DTO的客户端必须对其进行反序列化,因此需要其类路径中的实体类,即使它只使用DTO的接口。

解决方案3:使用Spring的转换器或任何其他外部化Bean进行此转换

解决方案3是我也更喜欢的解决方案。但是我会创建一个映射器

public interface Mapper<S,T> {
     public T map(S source);
     public S map(T target);
}

可以使用modelmapper之类的映射框架来实现。

您还说每个实体都有一个转换器

不会随着我的域模型的增长而“扩展”。对于很多实体,我必须为每个新实体创建两个转换器(-

我认为您只需要为一个DTO创建两个转换器或一个映射器,因为您的DTO是面向域的。

一旦您开始在另一个服务中使用它,您就会认识到另一个服务通常应该或不能返回第一个服务所返回的所有值。您将开始为每个其他服务实现另一个映射器或转换器。

如果我从专用或共享DTO的优缺点开始,这个答案会很长,所以我只能请您阅读我的博客《服务层设计的优缺点》。

编辑

关于第三个解决方案:你更喜欢在哪里调用映射器?

在用例上方的层中。DTO是数据传输对象,因为它们将数据打包在最适合传输协议的数据结构中。因此,我将该层称为传输层。该层负责将用例的请求和结果对象从传输表示映射到传输表示,例如json数据结构。

编辑

我看到您可以将实体作为DTO构造函数参数传递。你也同意相反的观点吗?我是说,将DTO作为实体构造函数参数传递?

好问题。相反的情况对我来说并不合适,因为我会在实体中向传输层引入一个依赖项。这意味着传输层中的更改会影响实体,我不希望更详细的层中的更改会影响更抽象的层。

如果您需要将数据从传输层传递到实体层,您应该应用依赖反转原则。

引入一个接口,通过一组getter返回数据,让DTO实现它,并在实体构造函数中使用这个接口。请记住,这个接口属于实体层,因此不应该对传输层有任何依赖。

                                interface
 +-----+  implements     ||   +------------+   uses  +--------+
 | DTO |  ---------------||-> | EntityData |  <----  | Entity |
 +-----+                 ||   +------------+         +--------+

 类似资料:
  • 问题内容: 为什么联接不好或“慢”。我知道我再听一次。我找到了这句话 问题在于联接相对较慢,尤其是在非常大的数据集上,如果联接较慢,则您的网站也很慢。从磁盘上获取所有这些单独的信​​息位并再次将它们重新组合在一起需要花费很长时间。 来源 我一直以为他们很快,尤其是在查找PK时。他们为什么“慢”? 问题答案: 可伸缩性是关于预计算(缓存),扩展或将重复的工作缩减为基本要件的全部目的,以最大程度地减少

  • 我在Eclipse IDE中编写了JavaFx项目,它工作正常。 一旦我把它转换成Maven项目,我的FMXL文件就停止打开了。但是,如果我创建新的FXML文件并将它们放在相同的文件夹中,一切都会正常工作。 有没有办法在不重新创建所有FXML文件的情况下运行我的Maven项目? 我已经尝试过在代码中更改FXML文件的路径,将FXML移到src/main/resources包,或者简单地将代码从旧的

  • 如何正确转换dto到json在Java?我这样做就像下面使用: 问题在于格式化字段。在Dto我有我的日期在这种格式:但转换此dto到json字节后,我看到我的拆分为对象与许多属性如下: 在使用之后,我希望将中的所有属性以与转换之前相同的格式进行转换。如何做到这一点? 谢谢你的帮助!

  • 我尝试使用,但找不到有效地转换为毫秒的方法。我不是说这是最好的方法,如果你知道一些更好的,我会非常感谢一些指示。

  • 我找了无数的问题。但大多数问题如下。 实体- 但我不想这样。 若Dto中不存在实体中的属性值,我想知道如何将Dto转换为实体。 没有为[annotations,Lombok,jpa,…]指定代码,但是它存在! userService的所有方法都返回DTO。如何在Post实体中设置用户实体?我收到一个userDto返回,它只有用户名。 这种时候我该怎么办? 存储库不是直接从控制器调用的。 服务将无条

  • 我该怎么做才能更精确地定位这些点(±3km就足够精确了)?