项目转换DTO使用总结,常用技巧
mapstruct在当前轻量级框架开发中的重点使用,@Named注解使用示例,@AfterMapping与@BeforeMapping注解的详细常见用法,在转换DTO时,与过去常用的beanUtil转换有高性能的转换优势,编译期自动生成的mapper实现类能够更加优雅的来实现各种隐式类型转换,以实现快速而又敏捷的开发,告别臃肿的手动get、set与类型的强转
当前core-service引入版本如下
<properties>
<mapstruct.version>1.2.0.Final</mapstruct.version>
</properties>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
@Mapper
public interface ItemMapper {
//使用工厂方法获取Mapper实例
ItemMapper INSTANCE = Mappers.getMapper(ItemMapper.class);
ItemDTO toDTO(Item item);
}
@Mapper(componentModel = "spring")
public interface ItemMapper {
ItemDTO toDTO(Item item);
}
代码可以自动生成的Mapper接口,可以自动转换相同属性的字段,无需其他声明。
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface AwardMapper extends BaseMapper<AwardDTO, Award> {
}
需要注意的是:再不写任何转换代码的情况下,如果实体类中配置了其他关联的一对多或一对一的实体,DTO如果同样需要转换的话,就需要保证属性名一致才能正常转换。
对于基础数据类型会进行自动隐式的转换如int、long、String,Integer、Long等;
//生成的实现类代码可以看出来
@Component
public class AssemblerImpl implements Assembler {
@Override
public ProductDTO toDTO(Product product) {
if ( product == null ) {
return null;
}
ProductDTO productDTO = new ProductDTO();
if ( product.getProductId() != null ) {
//String自动转int
productDTO.setProductId( Integer.parseInt( product.getProductId() ) );
}
if ( product.getPrice() != null ) {
//Long转String
productDTO.setPrice( String.valueOf( product.getPrice() ) );
}
return productDTO;
}
}
查看mapping注解可以看到
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD})
public @interface Mapping {
String target();
String source() default "";
String dateFormat() default "";
String numberFormat() default "";
...
}
dateFormat与numberFormat即可用来进行转换操作
@Mapper(componentModel = "spring")
public interface Demo4Assembler {
@Mapping(target = "saleTime", dateFormat = "yyyy-MM-dd HH:mm:ss") //Date转换成String
@Mapping(target = "validTime", dateFormat = "yyyy-MM-dd HH:mm") //String转换成Date
ProductDTO toDTO(Product product);
}
// 活动开始时间
@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss",timezone="GMT+8")
private Timestamp startTime;
// 活动结束时间
@JsonFormat(pattern = "yyyy/MM/dd HH:mm:ss",timezone="GMT+8")
private Timestamp endTime;
private String price;
private Integer stock;
@Mapper(componentModel = "spring")
public interface Demo3Assembler {
@Mapping(target = "price", numberFormat = "#.00元")
@Mapping(target = "stock", numberFormat = "#个")
ProductDTO toDTO(Product product);
}
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DrawRecordDetailMapper extends BaseMapper<DrawRecordDetailDTO, DrawRecord> {
@Override
@Mappings({
@Mapping(target = "chanceId", source = "participationChanceId")
})
DrawRecordDetailDTO toDto(DrawRecord entity);
}
//将entity中activity实体中name属性装载至DrawRecordDetailDTO中activityName属性上
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface DrawRecordDetailMapper extends BaseMapper<DrawRecordDetailDTO, DrawRecord> {
@Override
@Mappings({
@Mapping(target = "activityName", source = "activity.name")
})
DrawRecordDetailDTO toDto(DrawRecord entity);
}
//将entity中活动规则属性,转换至ActivityDetailDTO中activityRule对象中
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ActivityDetailMapper extends BaseMapper<ActivityDetailDTO, Activity> {
@Override
@Mappings({
@Mapping(target = "activityRule.voteOverlapAvailable", source = "voteOverlapAvailable"),
@Mapping(target = "activityRule.voteMultipleOnceAvailable", source = "voteMultipleOnceAvailable")
})
ActivityDetailDTO toDto(Activity entity);
}
一个自定义映射器可以定义多个映射方法,匹配时,是以方法的入参和出参进行匹配的
如果绑定的映射中,存在多个相同的入参和出参方法,将会报错
使用样例:将list中多个code在映射时,转换为String用逗号隔开
定义映射器:
@Component
public class MediaServiceFormater {
public String format(List<String> serviceCodeList) {
return String.join(",", serviceCode);
}
}
mapper接口绑定映射器 【uses = {MediaServiceFormater.class}】
@Mapper(componentModel = "spring", uses = {MediaServiceFormater.class,unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MediaDetailMapper extends BaseMapper<MediaDetailDTO, Media> {
}
映射器中使用注解来定义转换方法,使用时,具体属性绑定方法;
定义映射器:
@Component
public class MapStructConverterUtil {
private static final Logger log = LoggerFactory.getLogger(MapStructConverterUtil.class);
public MapStructConverterUtil() {
}
@Named("getNormalImage")
public String getNormalImage(String images) {
if (StringUtils.isEmpty(images)) {
return null;
} else {
try {
if (images.contains("normal")) {
JSONObject imageJson = JSON.parseObject(images);
JSONObject mapJson = imageJson.getJSONObject("map");
JSONArray normalJson = mapJson.getJSONArray("normal");
Integer index = normalJson.getInteger(0);
JSONObject object = imageJson.getJSONArray("list").getJSONObject(index);
return object.getString("fileUrl");
}
} catch (Exception var7) {
log.error("normal图片解析出错,原images = [{}]", images);
}
return null;
}
}
}
使用时绑定映射器:【uses = {MapStructConverterUtil.class}】
@Mapper(componentModel = "spring", uses = {MapStructConverterUtil.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface ActivityUserDetailMapper extends BaseMapper<ActivityUserDetailDTO, Activity> {
@Override
@Mappings({
@Mapping(target = "img1", source = "images", qualifiedByName = "getNormalImage"),
@Mapping(target = "img2", source = "images", qualifiedByName = "getBgImage"),
@Mapping(target = "img3", source = "images", qualifiedByName = "getPosterImage")
})
ActivityUserDetailDTO toDto(Activity entity);
}
mapStruct还支持map集合的转换,可以对map进行隐式转换,支持对Key与value进行隐式转换
例如:日期转字符串,字符串转日期;同样支持@name注解转换形式,或使用qualifiedBy指定一个转换类(类中默认使用相同入参出参来匹配)
@Mapper(componentModel = "spring", uses = {ConverterUtil.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface SourceTargetMapper {
@MapMapping(keyQualifiedByName = "getValue", valueDateFormat = "dd.MM.yyyy")
Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}
多个字段映射到一个字段,单个字段快速特殊处理
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface GroupDetailOrderMapper extends BaseMapper<GroupDetailOrderDTO, GroupDetailOrder> {
@Override
@Mappings({
@Mapping(target = "userName", expression = "java(entity.getUserName() == null ? \"\" : new String(java.util.Base64.getDecoder().decode(entity.getUserName()), java.nio.charset.StandardCharsets.UTF_8))")
})
GroupDetailOrderDTO toDto(GroupDetailOrder entity);
}
@Mapper(componentModel = "spring", imports = DecimalUtils.class)
public interface Demo16Assembler {
@Mapping(target = "price", expression = "java(product.getPrice1() + product.getPrice2())")
@Mapping(target = "price2", expression = "java(DecimalUtils.add(product.getPrice1(), product.getPrice2()))")
ProductDTO toDTO(Product product);
}
使用@BeforeMapping与@AfterMapping注解来实现,需要定义默认方法在mapper接口中【java8后支持】
使用前注意:
在调用上下文上的映射方法之前/之后,不执行空检查参数。调用方需要确保在这种情况下不传递null;
@Mapper(componentModel = “spring”, uses = {MapStructConverterUtil.class, MediaConverterUtil.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MediaDetailMapper extends BaseMapper<MediaDetailDTO, Media> {
//前置执行
@BeforeMapping
default MediaDetailDTO setMediaName(Media media) {
MediaDetailDTO mediaDetailDTO = new MediaDetailDTO();
mediaDetailDTO.setName("默认名称");
return mediaDetailDTO;
}
@Override
@Mappings({
@Mapping(target = "img1", source = "images", qualifiedByName = "getNormalImage"),
@Mapping(target = "img2", source = "images", qualifiedByName = "getBgImage"),
@Mapping(target = "img3", source = "images", qualifiedByName = "getPosterImage"),
@Mapping(target = "cp", source = "contentProviderId", qualifiedByName = "getCP"),
@Mapping(target = "audioTrack", source = "audioDescription")
})
MediaDetailDTO toDto(Media media);
//后置执行
@AfterMapping
default void setMediaType(@MappingTarget MediaDetailDTO mediaDetailDTO, Media media) {
Integer type = media.getType();
Integer contentType = media.getContentType();
String contentTyp = String.format("%02d", contentType);
mediaDetailDTO.setMediaType(type + contentTyp);
}
}
利用继承关系,继承mapper自动生成的实现类,来重写增强
此转换方式,同样可以实现在dto转换后后置执行,实体真正实现类中代码简洁也较为优雅
需要注意的是,一定要先实现父类转换方法拿到结果后在进行后续增强转换
super.toDto(entity)
实现样例如下:
//实现类引入方式使用@Resource注解引入
@Autowired
@Resource(name = "activityDetailMapperImplPlus")
private ActivityDetailMapper activityDetailMapper;
@Component("activityDetailMapperImplPlus")
@RequiredArgsConstructor
public class ActivityDetailMapperImplPlus extends ActivityDetailMapperImpl {
private final VoteTargetRepository voteTargetRepository;
@Override
public ActivityDetailDTO toDto(Activity entity) {
ActivityDetailDTO activityDetailDTO = super.toDto(entity);
Long activityDetailDTOId = activityDetailDTO.getId();
Integer type = activityDetailDTO.getType();
boolean vote = LocalActConstant.ACT_TYPE_VOTE == type;
if (vote) {
Optional<List<VoteTarget>> voteOp = this.voteTargetRepository.findByActivityIdAndStatus(activityDetailDTOId, 1);
if (voteOp.isPresent()) {
List<VoteTarget> voteTargets = voteOp.get();
//累积投票
activityDetailDTO.setTotalVoteNum(voteTargets.stream()
.filter(voteTarget -> voteTarget.getCurrentNumber() != null)
.mapToInt(VoteTarget::getCurrentNumber).sum());
}
}
return activityDetailDTO;
}
}
如果实体类中,关联了其他一对多或多对多的引用类型,JPA在mapper层自动转换后,又需要对引用对象增加出参或出参数据结构转换时,这时候利用转换生成是实现类继承强化后,再绑定到mapper中即可。这样就无需在接口实现层进行循环处理
使用样例如下:
MarketingActivityDetailDTO中有 private List registrationEvents;
需要在BaseRegistrationEventDTO对象中增加出参
//报名人数
private Long totalRegister;
接口侧转换使用的MarketingActivityDetailMapper接口如下:引用必须要包含RegistrationEventSimpleMapperImplPlus.class
uses = {MapStructConverterUtil.class, TemplateParamValueMapper1.class, RegistrationEventSimpleMapperImplPlus.class}
@Mapper(componentModel = "spring", uses = {MapStructConverterUtil.class, TemplateParamValueMapper1.class, RegistrationEventSimpleMapperImplPlus.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface MarketingActivityDetailMapper extends BaseMapper<MarketingActivityDetailDTO, MarketingActivity> {
@Override
@Mappings({
@Mapping(target = "img1", source = "images", qualifiedByName = "getNormalImage"),
@Mapping(target = "img2", source = "images", qualifiedByName = "getBgImage"),
@Mapping(target = "img3", source = "images", qualifiedByName = "getPosterImage")
})
MarketingActivityDetailDTO toDto(MarketingActivity entity);
}
RegistationEventSimpleMapper如下
@Component("registrationEventSimpleMapperImplPlus")
@RequiredArgsConstructor
public class RegistrationEventSimpleMapperImplPlus extends RegistrationEventSimpleMapperImpl {
private final UserRegistrationRepository userRegistrationRepository;
@Override
public BaseRegistrationEventDTO toDto(RegistrationEvent entity) {
BaseRegistrationEventDTO baseRegistrationEventDTO = super.toDto(entity);
Long registrationEventId = baseRegistrationEventDTO.getId();
baseRegistrationEventDTO.setTotalRegister(
this.userRegistrationRepository.countByRegistrationEventId(registrationEventId));
return baseRegistrationEventDTO;
}
}
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po