说在最前面的事
下面的代码实现在文章末,如果觉得复制粘贴太麻烦,可以直接下载代码导入
故事
又是无所事事的一天,现在趁着下班时间把 MapStruct 写一下,为什么会有这东西呢?其实之前我没接触过这玩意儿,这不是到公司开始实习了嘛,然后就在一次培训时候说到这个玩意儿,之后下去学习了一下,你还别说,真的好用。现在我们来看看这是个啥玩意。
之前我们开始写类需要大量的 get、set 方法,之后 lombok 出现了,然后开始写 SQL,慢慢的出现了 mybatis,再之后呢,用 @Mapper 注解代替了 xml 配置文件注入,接着是个啥,mybatis-plus 出现了,直接再 mapper 接口上 extends BaseMapper< Bean >,在 service 接口上 extends IService< Bean > ,然后在实现类上 extends ServiceImpl< BeanMapper,Bean > implements interface 就 OK。
但是我们在很多的开发中,会有 entity,但是并不是所有的 entity 都能够适应程序中所需的对象,这样,VO、DO、BO、POJO 等等,他们都有一个共性就是都可能包含其他对象的多个字段。这时候我们需要将一个对象属性值 copy 到另一个对象里就需要大量的 get、set,这时候 MapStruct 就出现了
POJO :plain ordinary java object 无规则简单 java 对象
PO:persistent object 持久对象
VO:返回表示层对象
DO(Data Object):此对象与数据库表结构
DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象
BO(Business Object):业务对象,由 Service 层输出的封装业务逻辑的对象
AO(ApplicationObject):应用对象,在 Web 层与 Service 层之间抽象的复用对象模型, 极为贴近展示层,复用度不高
现在应该大概就明白了这是个啥玩意儿,它就是把一个对象的或者多个对象的属性值赋值给另一个对象,为了省去我们写大量 get、set 方法而出现的映射工具。这玩意儿,哎,啥也不是,接下来开干。
引入依赖
构建一个空的 maven 项目,之后引入依赖,install 后开干。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>mapStructDemo</artifactId> <version>1.0-SNAPSHOT</version> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>8</source> <target>8</target> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.12</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.2.0.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-jdk8</artifactId> <version>1.2.0.Final</version> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> </dependencies> </project>
简单 demo
好了,现在我们开始干,构建一个三个 bean 实例。分别为 user、user2、userDO
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import java.util.Date; /** * @author (https://www.wslhome.top) * @description * @date 2020-10-11 17:05 **/ @Data @AllArgsConstructor @NoArgsConstructor @ToString public class User { Integer id; String name; String sex; String className; String school; String birthday; }
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author (https://www.wslhome.top) * @description * @date 2020-10-11 17:05 **/ @Data @AllArgsConstructor @NoArgsConstructor @ToString public class User2 { Integer id; String name; String sex; String className; String school; String birthday; }
最后一个 userDO
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import java.util.Date; /** * @author(https://www.wslhome.top) * @description * @date 2020-10-11 17:07 **/ @Data @NoArgsConstructor @AllArgsConstructor @ToString public class UserDO { String userName; String userId; String userSex; String userClassName; String userSchool; String phone; Date date; }
然后基本工作做郝了,见证奇迹的时候到了,写一个 conver 接口
import org.mapstruct.InheritConfiguration; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Mappings; import org.mapstruct.factory.Mappers; /** * @author (https://www.wslhome.top) * 实体转换 * @date 2020-10-11 17:09 **/ @Mapper public interface Converter { Converter INSTANCE = Mappers.getMapper(Converter.class); /** * user转user2 **/ User2 UserToUser2(User user); /** * User转UserDO实现 * */ @Mappings(value = { @Mapping(source = "id",target = "userId"), @Mapping(source = "name", target = "userName"), @Mapping(source = "sex", target = "userSex"), @Mapping(source = "className",target = "userClassName" ), @Mapping(source = "school",target = "userSchool"), //使用expression指定处理target @Mapping(target = "date",expression = "java(new java.util.Date())") }) UserDO ConverUser2UserDO(User user); }
看到没,看到没,就是这么简单。
在接口上打上 @Mapper 标记,注意包别引入错了,我们要引入的是 import org.mapstruct.Mapper;千万不要引成 mybatis 的。没错,就是这样就实现了。
然后 Converter INSTANCE = Mappers.getMapper(Converter.class);创建一个实例,也可在要用的地方创建。
也许你会说上面的为什么没有 @Mapping 或者 @Mapping 注解,下面的却要有。其实这就是做的一个映射。如果字段名不一致就需要用 @Mapping 来指定那个属性映射哪个属性,如果一个 bean 里面有很多属性,只要有超过两个属性名称字段不一样就用 @Mappings,如果字段名一样就什么都不用写
@Mapping 中有很多属性,作为入门 demo 就只需要知道 source 和 target 就可以了,source 指原来 bean 属性,target 即将要赋值的属性。从接口上可以容易看出属性是谁的转谁的。
接下来测试一下
import org.junit.jupiter.api.Test; import java.util.Date; /** * @author (https://www.wslhome.top) * @description 实体转化测试类 * @date 2020-10-11 17:10 **/ public class ConverterTest { /** * user转user2 * */ @Test void userToUser2Test(){ User user = new User(123,"sirwsl","男","计算机科学与技术","YMU","1997-01-16"); User2 user2 = Converter.INSTANCE.UserToUser2(user); System.out.println(user2); } /** * user转userDO测试 * */ @Test void user2UserDOTest(){ User user = new User(123,"sirwsl","男","计算机科学与技术","YMU","1997-01-16"); UserDO userDO = Converter.INSTANCE.ConverUser2UserDO(user); System.out.println(userDO.toString()); } }
多转一
当然在实际的开发的时候,往往是多个对象的属性转为一个特定的 bean。这时候就需要用多个转一个。直接来代码,废话不多说,照葫芦画瓢,要学习的文章末尾有压缩包,直接下来导入运行,不理解的自己敲一遍也就明白了。
建立四个实体集 Result、SC、StuAndRe、Student
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author(https://www.wslhome.top) * @description 成绩表 * @date 2020-10-11 20:11 **/ @Data @AllArgsConstructor @NoArgsConstructor @ToString public class Result { private Integer id; private String name; private String type; private Integer days; }
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author (https://www.wslhome.top) * @description * @date 2020-10-11 20:22 **/ @Data @AllArgsConstructor @NoArgsConstructor @ToString public class SC { private Integer score; private String level; private String test; }
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author wangshilei (https://www.wslhome.top) * @description 学生与成绩合并 * @date 2020-10-11 20:12 **/ @Data @AllArgsConstructor @NoArgsConstructor @ToString public class StuAndRe { private Integer SId; private String SName; private String RName; private String RType; private Integer RDays; private Integer score; private String level; private String test; }
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author (https://www.wslhome.top) * @description 学生表 * @date 2020-10-11 20:10 **/ @Data @AllArgsConstructor @NoArgsConstructor @ToString public class Student { private Integer id; private String name; private String sex; }
接下来还是一样,写一个 Conver 接口,名称自己随便名命一个就好了,然后记得加上 @Mapper 注解,别导错包。
import org.mapstruct.*; import org.mapstruct.factory.Mappers; /** * @author wangshilei (https://www.wslhome.top) * @description 转换接口 * @date 2020-10-11 20:12 **/ @Mapper public interface Converter { Converter DISTANCE = Mappers.getMapper(Converter.class); /** * Result与Student合并为StuAndRe * */ @Mappings(value = { @Mapping(source = "result.name",target = "RName"), @Mapping(source = "result.type",target = "RType"), @Mapping(source = "result.days",target = "RDays"), @Mapping(source = "student.id",target = "SId"), @Mapping(source = "student.name", target = "SName"), // constant注入产量值 @Mapping(target = "score", constant = "80"), @Mapping(target = "level", constant = "C4") }) StuAndRe ResStu2StuAndRe(Result result,Student student); /** * 三个实体合并属性 * @param result : * @param student : * @param v1 : * @param v2 : * @return demo2.StuAndRe * @author wangshilei * @date 2020/10/12 10:29 **/ @InheritConfiguration(name = "ResStu2StuAndRe") @Mappings(value = { @Mapping( source = "v1",target = "score"), @Mapping( source = "v2",target = "level") }) StuAndRe AllString2StuAndRe(Result result,Student student,String v1,String v2); /** * Result、SC与Student合并为StuAndRe **/ @Mappings(value = { @Mapping(source = "result.name",target = "RName"), @Mapping(source = "result.type",target = "RType"), @Mapping(source = "result.days",target = "RDays"), @Mapping(source = "student.id",target = "SId"), @Mapping(source = "student.name", target = "SName"), // 设置默认值 @Mapping(source = "sc.test",target = "test",defaultValue = "this is test") }) StuAndRe All2StuAndRe(Result result,Student student,SC sc); /** * 跟新bean,@MappingTarget后面的对象不变**/ @InheritConfiguration(name = "All2StuAndRe") void update(@MappingTarget StuAndRe stuAndRe,SC sc); /** * Result与字符串合并为StuAndRe **/ @Mappings(value = { @Mapping(source = "result.name",target = "RName"), @Mapping(source = "result.type",target = "RType"), @Mapping(source = "result.days",target = "RDays"), @Mapping(source = "ids",target = "SId"), @Mapping(source = "names", target = "SName"), }) StuAndRe Result2StuAndRe(Result result,String ids,String names); }
如果你看到这里有些地方要注意一下了
PS:我们建立接口的时候,其实也可以建立抽象类,interface 和 abstract 其实是一样的。
PS2:@InheritConfiguration 其实就是为了不重复,减少代码量。采用 @InheritConfiguration 然后指定方法名,然后就可以将指定的哪个方法的 @Mappers 中的实现全部复制下来。
PS3:@Mapper 中有 constant 属性,是用来给定常量值的
PS4:defaultValue 属性是当它为 null 时候会进行赋值
PS5:@MappingTarget 是用来更新 bean 用的,它后面的 bean 值不改变,另一个更新。(可能说错了,有大佬还希望指出。自己写 demo 时候发现就是这样的)
接下来开始写测试
import org.junit.jupiter.api.Test; /** * @author (https://www.wslhome.top) * @description * @date 2020-10-11 20:13 **/ public class DemoTest { /** * Result与Student实体合并 * */ @Test void StuAndReTest(){ Student student = new Student(123,"sirwsl","男"); Result result = new Result(1,"MapStruct","Java",3); StuAndRe stuAndRe = Converter.DISTANCE.ResStu2StuAndRe(result,student); System.out.println(stuAndRe.toString()); } /** * 接收原有参数 * Result与Student实体、字符串合并 * */ @Test void AllStringAndReTest(){ Student student = new Student(123,"sirwsl","男"); Result result = new Result(1,"MapStruct","Java",3); StuAndRe stuAndRe = Converter.DISTANCE.AllString2StuAndRe(result,student,"80","A1"); System.out.println(stuAndRe.toString()); } /** * Result\SC\Student三个实体合并 * **/ @Test void StuAndReTest2(){ Student student = new Student(123,"sirwsl","男"); Result result = new Result(1,"MapStruct","Java",3); SC sc = new SC(9,"B4",null); StuAndRe stuAndRe = Converter.DISTANCE.All2StuAndRe(result,student,sc); System.out.println(stuAndRe.toString()); } /** * 实体集更新 * **/ @Test void update(){ Student student = new Student(100,"sirwsl","男"); Result result = new Result(1,"MapStruct","Java",3); SC sc = new SC(9,"B4",null); StuAndRe stuAndRe = Converter.DISTANCE.All2StuAndRe(result,student,sc); System.out.println("更新前:"+stuAndRe.toString()); sc = new SC(100,"666666","test123456"); Converter.DISTANCE.update(stuAndRe,sc); System.out.println("更新后:"+stuAndRe.toString()); } /** * Result实体与字符串 * **/ @Test void StuAndReTest3(){ Result result = new Result(1,"MapStruct","Java",3); StuAndRe stuAndRe = Converter.DISTANCE.Result2StuAndRe(result,"123","sirwsl"); System.out.println(stuAndRe.toString()); } }
指定类型转化
当我们要转化一个类,但是其中一些属性需要经过计算之类的得到,大概就是这样。这里我们将 user1、user2 相互转化时有字符串转 bool 类型,自定义转换类 change 进行转换。
构建两个 bean 为 user1,user2
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author * @date 2020/10/12-9:09 **/ @Data @AllArgsConstructor @NoArgsConstructor @ToString public class User1 { private Integer id; private boolean flag; private String str; }
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author * @date 2020/10/12-9:09 **/ @Data @AllArgsConstructor @NoArgsConstructor @ToString public class User2 { private Integer id; private String flag; private String text; }
然后再来一个转换类。
/** * @author * @date 2020/10/12-9:15 **/ public class Exchange { public String boolToString(boolean flag){ if (flag) { return "yes"; }else { return "no"; } } public boolean stringToBool(String str){ if (str.equals("yes")){ return true; }else if(str.equals("no")){ return false; } return false; } }
继续刚刚的操作,定义接口
import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.factory.Mappers; /** * @author * @date 2020/10/12-9:33 **/ @Mapper(uses = {Exchange.class}) public interface Converter { Converter DESTAINCE = Mappers.getMapper(Converter.class); /** * User1转User2 * @param user : * @return demo3.User2 * @author wangshilei * @date 2020/10/12 10:25 **/ @Mapping(source = "str",target = "text") User2 user1ToUser2(User1 user); }
来个 demoTest 看看
import org.junit.jupiter.api.Test; /** * @author WangShilei * @date 2020/10/12-9:46 **/ public class DemoTest { @Test void user1ToUser2(){ User1 user1 = new User1(1,true,"tests"); User2 user2 = Converter.DESTAINCE.user1ToUser2(user1); System.out.println(user2.toString()); } }
集合转化
除了上面的当然还有集合转化
构建两个 bean 为 User1,User2.
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author * @date 2020/10/12-10:38 **/ @AllArgsConstructor @NoArgsConstructor @ToString @Data public class User1 { private Integer id; private String name; private Integer age; }
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author * @date 2020/10/12-10:38 **/ @AllArgsConstructor @NoArgsConstructor @ToString @Data public class User2 { private Integer id; private String name; }
然后接口
import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; import java.util.List; /** * @author * @date 2020/10/12-10:41 **/ @Mapper public interface Converter { Converter INSATNCE = Mappers.getMapper(Converter.class); /** * user1转User2 列表 * @param user : User1 * @return User2 * @author wangshilei * @date 2020/10/12 10:42 **/ List<User2> user1ToUser2(List<User1> user); }
然后测试
import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.List; /** * @author * @date 2020/10/12-10:40 **/ public class DemoTest { /**User1转User2**/ @Test void user1ToUser2Test(){ List<User1> user1 = new ArrayList<>(); user1.add(new User1(1,"zhangSang",18)); user1.add(new User1(2,"sirWSL",22)); user1.add(new User1(3,"wangWu",13)); user1.add(new User1(4,"liSi",45)); List<User2> user2s = Converter.INSATNCE.user1ToUser2(user1); System.out.println(user2s.toString()); } }
枚举类型转化
构建三个实体 User、UserDO、UserPojo
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author * @date 2020/10/12-11:14 **/ @AllArgsConstructor @NoArgsConstructor @ToString @Data public class User { public enum Grade{ // BAD,GOODS,BEET,GRADE; } private Grade grade; }
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author WangShilei * @date 2020/10/12-11:14 **/ @AllArgsConstructor @NoArgsConstructor @ToString @Data public class UserDO { public enum Level{ // BAD,GOODS,BEET,GRADE; } private Level level; }
import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; /** * @author * @date 2020/10/12-11:14 **/ @AllArgsConstructor @NoArgsConstructor @ToString @Data public class UserPojo { public enum Level{ // BAD123,GOODS123,BEET123,GRADE123; } private Level level; }
然后接口
import org.mapstruct.*; import org.mapstruct.factory.Mappers; /** * @author WangShilei * @date 2020/10/12-11:19 **/ @Mapper public interface Converter { Converter INSTANCE = Mappers.getMapper(Converter.class); /** * User转UserDO枚举类 枚举名称不同,值相同 * @param user : * @return null * @author wangshilei * @date 2020/10/12 11:21 **/ @Mapping(source="grade", target="level") UserDO userToUserDo(User user); /** * User转UserPOJO枚举类 枚举名称不同,值不同 * BAD,GOODS,BEET,GRADE;BAD123,GOODS123,BEET123,GRADE123; * @param user : * @return null * @author wangshilei * @date 2020/10/12 11:21 **/ @ValueMappings(value = { @ValueMapping(source = "BAD",target = "BAD123"), @ValueMapping(source = "GOODS",target = "GOODS123"), @ValueMapping(source = "BEET",target = "BEET123"), @ValueMapping(source = "GRADE",target = "GRADE123"), //映射失败时值为null @ValueMapping(source= MappingConstants.ANY_UNMAPPED, target=MappingConstants.NULL) }) UserPojo.Level userToUserPojo(User.Grade user); }
然后测试
import org.junit.jupiter.api.Test; /** * @author * @date 2020/10/12-11:25 **/ public class DemoTest { @Test void userEnumTest(){ User user = new User(); user.setGrade(User.Grade.GRADE); UserDO userDO = Converter.INSTANCE.userToUserDo(user); System.out.println(userDO.toString()); } @Test void userEnumTest2(){ User user = new User(); user.setGrade(User.Grade.GRADE); UserPojo.Level use= Converter.INSTANCE.userToUserPojo(user.getGrade()); System.out.println(use.toString()); } }
写累了,md,之前的几个应该对这个东东有所了解了,如果枚举没看懂,总之也不常用,要学去问度娘去。
如何实现
其实看到这里要是使用,你肯定会使用了,但是如何实现的,你肯定还很懵逼,现在随便说说,帮助理解,我本就是一介莽夫,说不出什么好点的解释来,需要更好的自己看文档去。
在实现上,我们打上 mapper 注解之后,在我们运行时候,由于我们定义的是接口,所以在实现上它会对接口进行一个实现,例如接口时 Conver 那么他的实现类就是 ConverImpl,这个东西你可以在编译后的文件中看到,这里我们随便拉一个看看,帮助理解。
以第一个简单 demo 实现后的来看
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package demo1; import java.text.SimpleDateFormat; import java.util.Date; public class ConverterImpl implements Converter { public ConverterImpl() { } public User2 UserToUser2(User user) { if (user == null) { return null; } else { User2 user2 = new User2(); user2.setId(user.getId()); user2.setName(user.getName()); user2.setSex(user.getSex()); user2.setClassName(user.getClassName()); user2.setSchool(user.getSchool()); user2.setBirthday(user.getBirthday()); return user2; } } public UserDO ConverUser2UserDO(User user) { if (user == null) { return null; } else { UserDO userDO = new UserDO(); userDO.setUserSex(user.getSex()); userDO.setUserClassName(user.getClassName()); userDO.setUserName(user.getName()); if (user.getId() != null) { userDO.setUserId(String.valueOf(user.getId())); } userDO.setUserSchool(user.getSchool()); userDO.setDate(new Date()); return userDO; } } public User ConverUserDO2User(UserDO userDO) { if (userDO == null) { return null; } else { User user = new User(); if (userDO.getDate() != null) { user.setBirthday((new SimpleDateFormat("yy-MM-dd hh:mm:ss")).format(userDO.getDate())); } user.setSex(userDO.getUserSex()); user.setName(userDO.getUserName()); user.setClassName(userDO.getUserClassName()); user.setId(this.getage(userDO.getUserId())); return user; } } public UserVO User2UserVO(User user) { if (user == null) { return null; } else { UserVO userVO = new UserVO(); userVO.setId(user.getId()); userVO.setName(user.getName()); userVO.setAge(this.getage(user.birthday)); return userVO; } } }
大概就是这样,它编译时候会进行一个 get、set 实现就是这么简单。
nnd,就写到这里了,毕竟时利用下班后的休息时间写的。over
这里时上面的代码的压缩包:mapStruct.zip
这里时上面的代码的压缩包:mapStruct.zip
这里时上面的代码的压缩包:mapStruct.zip
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于