【问题标题】:How to map collections of immutable objects in Dozer如何在 Dozer 中映射不可变对象的集合
【发布时间】:2016-02-02 13:18:39
【问题描述】:

this answer 的启发,我编写了一个自定义转换器(您可以在Github repo 中找到整个工作示例)。用于 Dozer 之间的转换:

public class MyEntity {
    private List<ObjectId> attachmentIds;

    public List<ObjectId> getAttachmentIds() { return attachmentIds; }

    public void setAttachmentIds(List<ObjectId> attachmentIds) {
        this.attachmentIds = attachmentIds;
    }
}

及其 DTO:

public class MyEntityDto {
    private List<FileDataDto> attachments;

    public List<FileDataDto> getAttachments() { return attachments; }

    public void setAttachments(List<FileDataDto> attachments) {
        this.attachments = attachments;
    }
}

MyEntity 仅保存存储在 Mongo 数据库中的文件的 ID。它以 JSON 格式发送到前端的 DTO 应该包含文件的 id 和文件名(这是 FileDataDto 类的内容)。我的转换器:

public class FileIdToFileDataConverter extends DozerConverter<ObjectId, FileDataDto> {
    public FileIdToFileDataConverter() {super(ObjectId.class, FileDataDto.class); }

    @Override
    public FileDataDto convertTo(ObjectId source, FileDataDto destination) {
        if (source == null) {
            return null;
        }
        FileDataDto fileData = destination == null ? new FileDataDto() : destination;
        fileData.setId(source.toString());
        // fetch the file from repository and update the name from db
        fileData.setFilename("myfile.txt");
        return fileData;
    }

    @Override
    public ObjectId convertFrom(FileDataDto source, ObjectId destination) {
        return source == null ? null : new ObjectId(source.getId());
    }
}

转换在MyEntity -> MyEntityDto 方向上按预期工作。然而,它在相反的情况下失败了。它使用由 Dozer 创建的ObjectId(作为destination 参数传递)而不是转换器返回的那个。本次测试

@Test
public void dtoToMyEntity() {
    MyEntityDto dto = new MyEntityDto();
    FileDataDto fileData = new FileDataDto();
    fileData.setFilename("file.txt");
    fileData.setId(new ObjectId().toString());
    dto.setAttachments(Arrays.asList(fileData));
    MyEntity myEntity = mapper.map(dto, MyEntity.class);
    assertEquals(fileData.getId(), myEntity.getAttachmentIds().get(0).toString());
}

失败并显示示例消息:

org.junit.ComparisonFailure: 
  Expected :56b0a9d110a937fc32a6db18
  Actual   :56b0a9d110a937fc32a6db19

您可以在Github repo 中找到我使用的整个测试和配置。

如何让转换器双向工作?

【问题讨论】:

    标签: java immutability dozer


    【解决方案1】:

    与dozer中的bug有关,导致通过API映射时没有使用自定义转换器:https://github.com/DozerMapper/dozer/issues/242

    因此您可以通过 xml 提供任一映射

    <?xml version="1.0" encoding="UTF-8"?>
    <mappings xmlns="http://dozer.sourceforge.net"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://dozer.sourceforge.net
              http://dozer.sourceforge.net/schema/beanmapping.xsd">
        <configuration>
            <custom-converters>
                <converter type="com.example.mapping.FileIdToFileDataConverter">
                    <class-a>org.bson.types.ObjectId</class-a>
                    <class-b>com.example.mapping.entity.FileDataDto</class-b>
                </converter>
            </custom-converters>
        </configuration>
        <mapping>
            <class-a>com.example.mapping.entity.MyEntity</class-a>
            <class-b>com.example.mapping.entity.MyEntityDto</class-b>
            <field>
                <a>attachmentIds</a>
                <b>attachments</b>
                <a-hint>org.bson.types.ObjectId</a-hint>
                <b-hint>com.example.mapping.entity.FileDataDto</b-hint>
            </field>
        </mapping>
    </mappings>
    

    然后

    mapper.setMappingFiles(Arrays.asList("dozerconfig.xml"));
    

    或者,如果您不想使用 xml,您可以创建一个使用自己的 ObjectIdFactory 的解决方法

    mapping(type(ObjectId.class).beanFactory(ObjectIdFactory.class), FileDataDto.class)
        .fields(this_(), this_(), customConverter(FileIdToFileDataConverter.class));
    

    还有工厂类

    public class ObjectIdFactory implements BeanFactory {
        @Override
        public Object createBean(Object source, Class<?> sourceClass, String targetBeanId) {
            if (source == null) {
                return null;
            }
            if (source instanceof ObjectId) {
                return source; // we can return source, because it's immutable
            }
            if (source instanceof String) {
                return new ObjectId((String) source);
            }
            if (source instanceof FileDataDto) {
                return new ObjectId(((FileDataDto) source).getId());
            }
            throw new MappingException("ObjectId should be of type ObjectId, String or FileDataDto");
        }
    }
    

    此解决方法有效的原因以及 id 不匹配的原因

    Dozer 默认使用类的无参数构造函数来实例化空值。 ObjectId 是不可变类,它的无参数构造函数基于时间戳创建新实例。

    【讨论】:

      【解决方案2】:

      一个更简单的替代方法是使用MapStruct 它支持开箱即用的不可变 (including Lombok's and Immutable's builders)。

      最小代码示例(来自文档):

      @Mapper
      public interface CarMapper { 
          CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
          CarDto carToCarDto(Car car);
      }
      
      // Usage:
      CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-07-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-11-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多