【Java】实体类转换框架 MapStruct
简单尝试了下发现比Dozer还有BeanUtil还方便小巧
注解的作用是在生成字节码文件时实现具体GetterSetter方法,实际转换时就是赋值操作,嘎嘎快
参考文章:
1 | https: //juejin.cn/post/7140149801991012365 |
引入必须的依赖:
lombok一般项目都会添加,这里我就只放这两个
1 2 3 4 5 6 7 8 9 10 | < dependency > < groupId >org.mapstruct</ groupId > < artifactId >mapstruct</ artifactId > < version >1.5.0.Final</ version > </ dependency > < dependency > < groupId >org.mapstruct</ groupId > < artifactId >mapstruct-processor</ artifactId > < version >1.5.0.Final</ version > </ dependency > |
一、入门案例:
两个需要相互转换的实体类,演示用:
DTO
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package cn.cloud9.server.test.mapstruct; import lombok.*; import lombok.experimental.Accessors; @Data @Builder @ToString @Accessors (chain = true ) @AllArgsConstructor @NoArgsConstructor public class DemoDTO { private Integer fieldA; private Boolean fieldB; private String fieldC; } |
Entity
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package cn.cloud9.server.test.mapstruct; import lombok.*; import lombok.experimental.Accessors; @Data @Builder @ToString @Accessors (chain = true ) @AllArgsConstructor @NoArgsConstructor public class DemoEntity { private Integer fieldD; private Boolean fieldE; private String fieldF; } |
转换器接口编写:
如果需要转换更多的实体类,都可以在接口声明方法,然后声明映射的字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | package cn.cloud9.server.test.mapstruct; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; /** * 转换器接口 */ @Mapper public interface DemoConverter { DemoConverter INSTANCE = Mappers.getMapper(DemoConverter. class ); /* DTO -> ENTITY */ @Mappings (value = { @Mapping (source = "fieldA" , target = "fieldD" ), @Mapping (source = "fieldB" , target = "fieldE" ), @Mapping (source = "fieldC" , target = "fieldF" ), }) DemoEntity dto2entity(DemoDTO demoDTO); /* ENTITY -> DTO */ @Mappings (value = { @Mapping (source = "fieldD" , target = "fieldA" ), @Mapping (source = "fieldE" , target = "fieldB" ), @Mapping (source = "fieldF" , target = "fieldC" ), }) DemoDTO entity2dto(DemoEntity demoEntity); } |
测试方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test public void converterTest() { DemoDTO build = DemoDTO.builder() .fieldA( 1001 ) .fieldB(Boolean.TRUE) .fieldC( "TEST" ) .build(); DemoEntity demoEntity = DemoConverter.INSTANCE.dto2entity(build); DemoDTO demoDTO = DemoConverter.INSTANCE.entity2dto(demoEntity); log.info( "demoEntity -> {}" , demoEntity); log.info( "demoDTO -> {}" , demoDTO); } |
执行结果:
1 2 3 4 | 22 : 52 : 20.079 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoEntity -> DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) 22 : 52 : 20.083 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoDTO -> DemoDTO(fieldA= 1001 , fieldB= true , fieldC=TEST) Process finished with exit code 0 |
二、集合转换支持
也是根据参数类型推断,自动生成了迭代集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | /* List<ENTITY -> DTO> */ @Mappings (value = { @Mapping (source = "fieldD" , target = "fieldA" ), @Mapping (source = "fieldE" , target = "fieldB" ), @Mapping (source = "fieldF" , target = "fieldC" ), }) List<DemoDTO> entity2dto(List<DemoEntity> demoEntity); /* List<DTO -> ENTITY> */ @Mappings (value = { @Mapping (source = "fieldA" , target = "fieldD" ), @Mapping (source = "fieldB" , target = "fieldE" ), @Mapping (source = "fieldC" , target = "fieldF" ), }) List<DemoEntity> dto2entity(List<DemoDTO> demoDTO); |
测试方法:
1 2 3 4 5 6 7 8 9 10 | ArrayList<DemoDTO> objects = new ArrayList<>(); for ( int i = 0 ; i < 10 ; i++) { objects.add(DemoDTO.builder() .fieldA( 1001 ) .fieldB(Boolean.TRUE) .fieldC( "TEST" ) .build()); } List<DemoEntity> demoEntityList = DemoConverter.INSTANCE.dto2entity(objects); demoEntityList.forEach(d -> log.info( "de {}" , d)); |
执行结果:
1 2 3 4 5 6 7 8 9 10 | 23 : 16 : 13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) 23 : 16 : 13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) 23 : 16 : 13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) 23 : 16 : 13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) 23 : 16 : 13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) 23 : 16 : 13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) 23 : 16 : 13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) 23 : 16 : 13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) 23 : 16 : 13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) 23 : 16 : 13.845 [main] INFO cn.cloud9.spring.BeanFactoryTest - de DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST) |
三、映射配置继承与反向继承
只需要提供一份映射注解信息即可,其他方法通过继承注解获取映射信息
同理,要反向转换时,提供了一个反向注解:
两个注解都需要你提供方法名来查找映射配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | package cn.cloud9.server.test.mapstruct; import org.mapstruct.*; import org.mapstruct.factory.Mappers; import java.util.List; /** * 转换器接口 */ @Mapper public interface DemoConverter { DemoConverter INSTANCE = Mappers.getMapper(DemoConverter. class ); /* DTO -> ENTITY */ @Mappings (value = { @Mapping (source = "fieldA" , target = "fieldD" ), @Mapping (source = "fieldB" , target = "fieldE" ), @Mapping (source = "fieldC" , target = "fieldF" ), }) DemoEntity dto2entity(DemoDTO demoDTO); /* ENTITY -> DTO */ // @Mappings(value = { // @Mapping(source = "fieldD", target = "fieldA"), // @Mapping(source = "fieldE", target = "fieldB"), // @Mapping(source = "fieldF", target = "fieldC"), // }) // DemoDTO entity2dto(DemoEntity demoEntity); @InheritInverseConfiguration (name = "dto2entity" ) DemoDTO entity2dto(DemoEntity demoEntity); /* List<ENTITY -> DTO> */ // @Mappings(value = { // @Mapping(source = "fieldD", target = "fieldA"), // @Mapping(source = "fieldE", target = "fieldB"), // @Mapping(source = "fieldF", target = "fieldC"), // }) @InheritConfiguration (name = "entity2dto" ) List<DemoDTO> entity2dto(List<DemoEntity> demoEntity); /* List<DTO -> ENTITY> */ // @Mappings(value = { // @Mapping(source = "fieldA", target = "fieldD"), // @Mapping(source = "fieldB", target = "fieldE"), // @Mapping(source = "fieldC", target = "fieldF"), // }) @InheritInverseConfiguration (name = "entity2dto" ) List<DemoEntity> dto2entity(List<DemoDTO> demoDTO); } |
四、是否阻止默认映射
默认同名同类型字段直接映射
这里对两个实体类新加了同一个字段:
1 | private Long filedG; |
测试时添加该属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 | @Test public void converterTest() { DemoDTO build = DemoDTO.builder() .fieldA( 1001 ) .fieldB(Boolean.TRUE) .fieldC( "TEST" ) .filedG(3003L) .build(); DemoEntity demoEntity = DemoConverter.INSTANCE.dto2entity(build); DemoDTO demoDTO = DemoConverter.INSTANCE.entity2dto(demoEntity); log.info( "demoEntity -> {}" , demoEntity); log.info( "demoDTO -> {}" , demoDTO); } |
执行结果可以发现,默认赋值上去了
1 2 3 4 | 23 : 34 : 51.780 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoEntity -> DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST, filedG= 3003 ) 23 : 34 : 51.787 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoDTO -> DemoDTO(fieldA= 1001 , fieldB= true , fieldC=TEST, filedG= 3003 ) Process finished with exit code 0 |
使用@BeanMapping注解可以阻止这种赋值行为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /** * 转换器接口 */ @Mapper public interface DemoConverter { DemoConverter INSTANCE = Mappers.getMapper(DemoConverter. class ); /* @BeanMapping(ignoreByDefault = true) 阻止MapStruct默认同名字段赋值行为 */ @BeanMapping (ignoreByDefault = true ) /* DTO -> ENTITY */ @Mappings (value = { @Mapping (source = "fieldA" , target = "fieldD" ), @Mapping (source = "fieldB" , target = "fieldE" ), @Mapping (source = "fieldC" , target = "fieldF" ), }) DemoEntity dto2entity(DemoDTO demoDTO); /* ENTITY -> DTO */ @InheritInverseConfiguration (name = "dto2entity" ) DemoDTO entity2dto(DemoEntity demoEntity); /* List<ENTITY -> DTO> */ @InheritConfiguration (name = "entity2dto" ) List<DemoDTO> entity2dto(List<DemoEntity> demoEntity); /* List<DTO -> ENTITY> */ @InheritInverseConfiguration (name = "entity2dto" ) List<DemoEntity> dto2entity(List<DemoDTO> demoDTO); } |
再次执行发现不再能够进行赋值处理了:
1 2 | 23 : 38 : 03.011 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoEntity -> DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST, filedG= null ) 23 : 38 : 03.015 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoDTO -> DemoDTO(fieldA= 1001 , fieldB= true , fieldC=TEST, filedG= null ) |
是否不继承@BeanMapping忽略默认映射的配置?
1 | https: //www.bilibili.com/video/BV1E5411n7HR?p=9 |
在视频中提到是不继承的,这里我加上了字段的赋值,并且调用反向转换的方法:
1 2 3 4 5 6 7 8 9 10 11 | DemoDTO build = DemoDTO.builder() .fieldA( 1001 ) .fieldB(Boolean.TRUE) .fieldC( "TEST" ) .fieldG(3003L) .build(); DemoEntity demoEntity = DemoConverter.INSTANCE.dto2entity(build); demoEntity.setFieldG(3003L); DemoDTO demoDTO = DemoConverter.INSTANCE.entity2dto(demoEntity); log.info( "demoEntity -> {}" , demoEntity); log.info( "demoDTO -> {}" , demoDTO); |
可以从执行结果发现,实际上是没有赋值的,说明是继承了@BeanMapping的效果了
1 2 3 4 | 23 : 41 : 02.989 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoEntity -> DemoEntity(fieldD= 1001 , fieldE= true , fieldF=TEST, fieldG= 3003 ) 23 : 41 : 02.994 [main] INFO cn.cloud9.spring.BeanFactoryTest - demoDTO -> DemoDTO(fieldA= 1001 , fieldB= true , fieldC=TEST, fieldG= null ) Process finished with exit code 0 |
五、注册成SpringBean
在前面的用法中都是直接声明一个单例使用,这里可以交给Spring处理:
@Mapper(componentModel = "spring")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | package cn.cloud9.server.test.mapstruct; import org.mapstruct.*; import org.mapstruct.factory.Mappers; import java.util.List; /** * 转换器接口 */ @Mapper (componentModel = "spring" ) public interface DemoConverter { DemoConverter INSTANCE = Mappers.getMapper(DemoConverter. class ); /* @BeanMapping(ignoreByDefault = true) 阻止MapStruct默认同名字段赋值行为 */ // @BeanMapping(ignoreByDefault = true) /* DTO -> ENTITY */ @Mappings (value = { @Mapping (source = "fieldA" , target = "fieldD" ), @Mapping (source = "fieldB" , target = "fieldE" ), @Mapping (source = "fieldC" , target = "fieldF" ), }) DemoEntity dto2entity(DemoDTO demoDTO); /* ENTITY -> DTO */ @InheritInverseConfiguration (name = "dto2entity" ) DemoDTO entity2dto(DemoEntity demoEntity); /* List<ENTITY -> DTO> */ @InheritConfiguration (name = "entity2dto" ) List<DemoDTO> entity2dto(List<DemoEntity> demoEntity); /* List<DTO -> ENTITY> */ @InheritInverseConfiguration (name = "entity2dto" ) List<DemoEntity> dto2entity(List<DemoDTO> demoDTO); } |
在需要调用的地方声明即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | package cn.cloud9.server.test.controller; import cn.cloud9.server.test.mapstruct.DemoConverter; import cn.cloud9.server.test.mapstruct.DemoDTO; import cn.cloud9.server.test.mapstruct.DemoEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @RequestMapping ( "/test/map-struct" ) public class MapStructController { @Resource private DemoConverter demoConverter; @GetMapping ( "/get" ) public DemoEntity converterTest() { DemoDTO build = DemoDTO.builder() .fieldA( 1001 ) .fieldB(Boolean.TRUE) .fieldC( "TEST" ) .fieldG(3003L) .build(); return demoConverter.dto2entity(build); } } |
测试结果:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· 葡萄城 AI 搜索升级:DeepSeek 加持,客户体验更智能
· 什么是nginx的强缓存和协商缓存
· 一文读懂知识蒸馏
2020-07-01 【Java,IDEA】创建自己的代码模版快速生成