如何在Dozer中映射不可变对象的集合

时间:2016-02-02 13:18:40

标签: java immutability dozer

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。它的DTO被发送到JSON的前端,应该包含文件的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 - &gt;中按预期工作{{1个方向。然而,它恰恰相反。它使用Dozer创建的MyEntityDto(作为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中找到我使用的整个测试和配置。

如何使转换器以两种方式工作?

2 个答案:

答案 0 :(得分: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");
    }
}

此workaroud工作的原因以及为什么ID不匹配

Dozer默认使用类的no-args构造函数来实例化空值。 ObjectId是不可变类,它的no-args构造函数根据时间戳创建新实例。

答案 1 :(得分:0)

一个更简单的选择是使用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);