当前位置: 首页 > 科技观察

实体类的属性映射怎么能少呢?

时间:2023-03-14 23:02:37 科技观察

我们都知道,随着一个项目越来越成熟,模块的划分也会越来越细。实体类一般存放在领域中,但是领域项目不应该被其他项目依赖,所以其他项目想要获取实体类数据时,需要在每个项目中写一个模型,自定义模型即可根据自身业务需要映射相应的实体属性。这样一来,测绘工程就显得不简单了。阿粉差点遇到问题...前言那么阿粉今天给大家带来一个插件叫mapstruct,专门用来处理domin实体类和模型类之间的属性映射。我们只需要定义mapper接口,mapstruct在编译的时候会自动帮我们实现这个映射接口,避免了麻烦复杂的映射实现。那么有的朋友可能要问了?为什么不用BeanUtils的copyProperties方法呢?不是可以实现属性的映射吗?这个啊,阿凡,一开始我也很好奇,于是和BeanUtils进行了深入的交流,最后发现BeanUtils是个大佬,只能映射到同一个属性,或者在case中对于相同的属性,它允许映射更少的对象属性;但是当映射的属性数据类型被修改或者映射的字段名被修改时,会导致映射失败。而mapstruct是个聪明的儿媳妇。她心思细腻,把我们可能遇到的各种情况都考虑到了(要是能找到这样的媳妇就好了,心里笑成猪了)如下是的本插件开源项目地址及各种示例:Github地址:https://github.com/mapstruct/mapstruct/使用示例:https://github.com/mapstruct/mapstruct-examples一、准备工作接下来,阿芬会和大家一起揭开这个机灵儿媳妇的真实面纱,所以我们还需要做一些准备工作。1.1.理解@Mapper注解。mybatis3.4.0加入的@Mapper注解是不再写mapper映射文件。我们只需要在dao层定义的接口上使用注解即可实现sql语句的编写,例如:@Select("select*fromuserwherename=#{name}")publicUserfind(Stringname);以上是一个简单的使用,虽然简单,但是确实体现了这个注解的优越性,至少少写了一个xml文件。不过阿粉,我今天不想和你讨论@Mapper注解。主要是想看看我机灵的老婆mapstruct,所以我只想说说@Mapper注解的componentModel属性。componentModel属性用于指定自动生成的接口实现类的组件类型。该属性支持四个值:default:这是默认情况。mapstruct不使用任何组件类型,可以通过Mappers.getMapper(Class)获取自动生成的实例对象。cdi:生成的mapper是一个application-scopedCDIbean,可以通过@Injectspring获取:@Component注解会自动添加到生成的实现类中,可以通过Spring的@Autowired方法注入。jsr330:在生成的实现类上会添加@javax.inject.Named和@Singleton注解,可以通过@Inject注解1.2获取。依赖包首先需要导入依赖包,主要由两个包组成:org.mapstruct:mapstruct:包含一些必要的注解,例如@Mapping。r如果我们使用的JDK版本高于1.8,我们在pom中引入依赖时,建议使用坐标:org.mapstruct:mapstruct-jdk8,这样可以帮助我们利用Java8的一些新特性。org.mapstruct:mapstruct-processor:注解处理器,根据注解自动生成mapper实现。org.mapstructmapstruct-jdk81.2.0.Finalorg.mapstructmapstruct-processor1.2.0.Final好了,准备工作做好了,接下来我们看看聪明的儿媳妇在哪里。2.简单玩一下2.1.定义实体类和映射类//实体类@Data@NoArgsConstructor@AllArgsConstructor@BuilderpublicclassUser{privateIntegerid;privateStringname;privateStringcreateTime;privateLocalDateTimeupdateTime;}//映射类VO1:与实体类完全一样@Data@NoArgsConstructor@AllArgsConstructor@BuilderpublicclassUserVO1{privateIntegerid;privateStringname;privateStringcreateTime;privateLocalDateTimeupdateTime;}//映射类VO1:比实体类少一个字段@Data@NoArgsConstructor@AllArgsConstructor@BuilderpublicclassUserVO2{privateIntegerid;privateString2name;privateStringname}接口定义;:当实体类与映射对象属性相同或映射对象属性值较小时:@Mapper(componentModel="spring")publicinterfaceUserCovertBasic{UserCovertBasicINSTANCE=Mappers.getMapper(UserCovertBasic.class);/***字段数量type数量相同,也可以使用工具BeanUtils实现类似的效果*@paramsource*@return*/UserVO1toConvertVO1(Usersource);UserfromConvertEntity1(UserVO1userVO1);/***字段数量相同类型,而且数量少:只能把more转换成less,所以没有fromConvertEntity2*@paramsource*@return*/UserVO2toConvertVO2(Usersource);}从上面的代码可以看出,声明了一个成员变量INSTANCE接口,父级是让客户客户端可以访问Mapper接口的实现2.3.使用@RestControllerpublicclassTestController{@GetMapping("convert")publicObjectconvertEntity(){Useruser=User.builder().id(1).name("张三").createTime("2020-04-0111:05:07").updateTime(LocalDateTime.now()).build();ListobjectList=newArrayList<>();objectList.add(user);//使用mapstructUserVO1userVO1=UserCovertBasic.INSTANCE.toConvertVO1(user);objectList.add("userVO1:"+UserCovertBasic.INSTANCE.toConvertVO1(user));objectList.add("userVO1转换回实体类user:"+UserCovertBasic.INSTANCE.fromConvertEntity1(userVO1));//输出转换结果objectList.add("userVO2:"+"|"+UserCovertBasic.INSTANCE.toConvertVO2(user));//使用BeanUtilsUserVO2userVO22=newUserVO2();BeanUtils.copyProperties(user,userVO22);objectList.add("userVO22:"+"|"+userVO22);returnobjectList;}}2.4.检查编译结果。使用IDE的反编译功能查看编译后自动生成的UserCovertBasic实现类UserCovertBasicImpl。内容如下:@ComponentpublicclassUserCovertBasicImplementsUserCovertBasic{publicUserCovertBasicImpl(){}publicUserVO1toConvertVO1(Usersource){if(source==null){returnnull;}else{UserVO1userVO1=newUserVO1();userVO1.setId(source.getId());userVO1.setName(source.getName());userVO1.setCreateTime(source.getCreateTime());userVO1.setUpdateTime(source.getUpdateTime());returnuserVO1;}}publicUserfromConvertEntity1(UserVO1userVO1){if(userVO1==null){returnnull;}else{Useruser=newUser();user.setId(userVO1.getId());user.setName(userVO1.getName());user.setCreateTime(userVO1.getCreateTime());user.setUpdateTime(userVO1.getUpdateTime());returnuser;}}publicUserVO2toConvertVO2(Usersource){if(source==null){returnnull;}else{UserVO2userVO2=newUserVO2();userVO2.setId(source.getId());userVO2.setName(source.getName());userVO2.setCreateTime(source.getCreateTime());returnuserVO2;}}}2.5。浏览器检查结果,过程完成。是不是感觉很简单?并且,阿芬温馨提醒:如果要转换一个集合,只需要将这里的实体类换成一个集合即可,例如:ListtoConvertVOList(Listsource);3.在不简单的情况下,上面已经描述了整个过程重新讲了一遍,相信大家对mapstruct已经有了基本的了解,接下来的情况就不一一展示所有代码了。毕竟篇幅有限,直接贴关键代码(因为不关键的和上面的内容一样,哈哈)3.1、类型不一致的实体类我们还是用User;映射对象UserVO3改为:@Data@NoArgsConstructor@AllArgsConstructor@BuilderpublicclassUserVO3{privateStringid;privateStringname;//实体类的属性为StringprivateLocalDateTimecreateTime;//实体类的属性为LocalDateTimeprivateStringupdateTime;}然后就是我们定义的接口会稍微修改一下:@Mappings({@Mapping(target="createTime",expression="java(com.java.mmzsblog.util.DateTransform.strToDate(source.getCreateTime()))")",})UserVO3toConvertVO3(Usersource);UserfromConvertEntity3(UserVO3userVO3);上述表达式指定的表达式如下:2018-01-1217:07:05",df);}}通过IDE的反编译功能查看编译后的实现类,结果是这样的:从图中我们可以看到,expres中定义的表达式sion用于在编译时转换目标字段createTime;然后你还会发现updateTime字段也自动从LocalDateTime类型转换为String类型阿芬总结:当字段类型不一致时,mapstruct会自动进行以下类型之间的类型转换:1.基本类型及其对应的封装类型。此时mapstruct会自动unbox。不需要人为处理2、其他基本类型的包装类型和字符串类型之间的类型转换,我们可以通过定义表达式进行指定的转换。3.2.字段名称不一致。我们仍然使用User作为实体类;映射对象UserVO4改为:@Data@NoArgsConstructor@AllArgsConstructor@BuilderpublicclassUserVO4{//实体类的属性名是idprivateStringuserId;//实体类的属性名是nameprivateStringuserName;privateStringcreateTime;privateStringupdateTime;}然后我们定义的接口稍微修改一下:@Mappings({@Mapping(source="id",target="userId"),@Mapping(source="name",target="userName")})UserVO4toConvertVO(Usersource);UserfromConvertEntity(UserVO4userVO4);通过IDE的反编译功能查看编译后的实现类。编译后的结果是这样的:阿芬总结:当字段名不一致时,使用@Mappings注解指定对应关系,编译后即可实现对应字段的赋值。很明显,mapstruct是通过读取我们配置的字段名的对应关系,帮我们分配到对应位置的。可以说是相当优秀了,但这也只是优秀而已,更优秀的请继续看:3.3、如果属性是枚举类型的实体类,应该改用UserEnum:@Data@NoArgsConstructor@AllArgsConstructor@BuilderpublicclassUserEnum{privateIntegerid;privateStringname;privateUserTypeEnumuserTypeEnum;}从映射对象UserVO5改为:@Data@NoArgsConstructor@AllArgsConstructor@BuilderpublicclassUserVO5{privateIntegerid;privateStringname;privateStringtype;}枚举对象为:@Getter@AllArgsConstructorpublicenumUserType00{Java","Java开发工程师"),DB("001","数据库管理员"),LINUX("002","Linux运维人员");privateStringvalue;privateStringtitle;}那么我们定义的接口还是定义为通常,它不会因为它是一个枚举而改变:@Mapping(source="userTypeEnum",target="type")UserVO5toConvertVO5(UserEnumsource);UserEnumfromConvertEntity5(UserVO5userVO5);通过IDE的反编译功能查看编译后的实现类。编译后的结果是这样的:很明显,mapstruct通过枚举类型String的内容帮我们把枚举类型转换成字符,并给类型赋值,可以说是细心做百万年船了。看来这个聪明的媳妇不仅优秀还细心……文中所有例子都已上传到github:https://github.com/mmzsblog/mapstructDemo