如何在 Dozer 中映射不可变对象的集合
How to map collections of immutable objects in Dozer
灵感来自 , I have wrote a custom conveter (you can find the whole working example in the Github repo)。推土机在以下之间转换:
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
class 的内容)。我的转换器:
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.
中找到我使用的整个测试和配置
如何让转换器双向工作?
它与推土机中的错误有关,导致通过 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));
和工厂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 默认使用 class 的无参数构造函数来实例化空值。 ObjectId 是不可变的 class 并且它的无参数构造函数根据时间戳创建新实例。
一个更简单的替代方法是使用 MapStruct It supports immutables out of the box (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);
灵感来自
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
class 的内容)。我的转换器:
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.
中找到我使用的整个测试和配置如何让转换器双向工作?
它与推土机中的错误有关,导致通过 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));
和工厂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 默认使用 class 的无参数构造函数来实例化空值。 ObjectId 是不可变的 class 并且它的无参数构造函数根据时间戳创建新实例。
一个更简单的替代方法是使用 MapStruct It supports immutables out of the box (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);