mapstruct学习笔记
mapstruct学习笔记
MapStruct是一个基于Java注解处理器的代码生成器,它专注于在Java Bean类型之间自动生成类型安全、高性能且无依赖的映射代码。MapStruct极大地简化了对象之间的映射过程,减少了手动编写映射代码的工作量。其主要特点包括:
- 类型安全:在编译时进行类型检查,确保映射的正确性。
- 高性能:生成的映射代码使用简单的方法调用,避免了反射带来的性能损耗。
- 灵活性:支持复杂的映射场景,如嵌套映射、集合映射、自定义转换规则等。
- 简洁性:通过注解定义映射规则,使得映射逻辑更加直观和简洁。
- 无依赖:不依赖于任何第三方库,易于集成到任何Java项目中。
MapStruct特别适用于多层架构的应用,如在持久层的实体和传输层的DTO或VO之间进行转换。
先引入对应的包
如果不依赖lombok
...
<properties>
<org.mapstruct.version>1.6.0</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>17</source>
<target>17</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
...
如果依赖lombok
....
<properties>
<java.version>17</java.version>
<org.mapstruct.version>1.6.0</org.mapstruct.version>
<org.projectlombok.version>1.18.34</org.projectlombok.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
<dependencies>
....
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
....
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<!-- mapstruct相关 start -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
<!-- mapstruct相关 end -->
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
为了更好体验,可以给IDEA安装MapStruct support插件
示例中使用的两个类
User.java
@Data
public class User {
private Integer id;
private String name;
private String mobile;
}
UserVo.java
@Data
public class UserVo {
private Integer id;
// 注意这里有两个属性不一样
private String nickName;
private String contactMobile;
}
1、基本的用法
1.1 两个对象属性名都相同
如果两个对象需要拷贝的属性值相同,只需下面两行代码
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserConverter {
UserConverter CONVERTER = Mappers.getMapper(UserConverter.class);
UserVo toUserVo(User user);
}
rebuild项目会自动生成如下代码
public class UserConverterImpl implements UserConverter {
@Override
public UserVo toUserVo(User user) {
if ( user == null ) {
return null;
}
UserVo userVo = new UserVo();
userVo.setId( user.getId() );
return userVo;
}
}
只要是两个对象里属性名相同的都会自动复制,不同的会忽略
并且两个属性的类型不同也没关系,mapstruct会智能转换成对应类型
1.2 两个对象有部分属性名不同
使用@Mapping注解即可
@Mapper
public interface UserConverter {
UserConverter CONVERTER = Mappers.getMapper(UserConverter.class);
@Mapping(source = "name", target = "nickName")
@Mapping(source = "mobile", target = "contactMobile")
UserVo toUserVo(User user);
}
如果对象里面的属性是对象,使用对象 . 属性操作即可
多个值转换也可以这样:
@Mappings({
@Mapping(source = "name", target = "nickName"),
@Mapping(source = "mobile", target = "contactMobile")
})
rebuild项目会自动生成如下代码
public class UserConverterImpl implements UserConverter {
@Override
public UserVo toUserVo(User user) {
if ( user == null ) {
return null;
}
UserVo userVo = new UserVo();
userVo.setNickName( user.getName() );
userVo.setContactMobile( user.getMobile() );
userVo.setId( user.getId() );
return userVo;
}
}
使用方式:
@Test
void contextLoads() {
User user = new User();
user.setId(1);
user.setMobile("123456789");
user.setName("张三");
// 调用方式
UserVo userVo = UserConverter.CONVERTER.toUserVo(user);
System.out.println(userVo.getNickName()); // 如期输出张三
}
一个方法怎够,其实接口里面还可以写多个转换方法
@Mapper
public interface UserConverter {
UserConverter CONVERTER = Mappers.getMapper(UserConverter.class);
@Mappings({
@Mapping(source = "name", target = "nickName"),
@Mapping(source = "mobile", target = "contactMobile")
})
UserVo toUserVo(User user);
// 从userVo拷贝属性到user,复用上面的mapping
@InheritInverseConfiguration
User toUser(UserVo userVo);
// 克隆user
User clone(User user);
}
@InheritInverseConfiguration
注解是指复用之前的mapping
映射,否则还需要再写一遍映射关系;如果有多个转换方法则需要指定复用哪个方法的映射
@InheritInverseConfiguration( name = "toUserVo" )
以上为最基础、常用的写法
1.3 其他配置
忽略
@Mapping(target = "id", ignore = true)
不拷贝id的值
默认值
@Mapping(target = "id", defaultValue = "100")
如果id值为空则设置为100
设置常量
@Mapping(target = "id", constant = "111")
设置id的值为111
格式化
@Mapping(source = "weight", target = "weight", numberFormat = "#.00")
格式数字
拷贝的属性会保留两位小数
User user = new User();
user.setWeight(100.5f);
UserVo userVo = UserConverter.CONVERTER.toUserVo(user);
// userVo的weight为String类型,类型不同会自动转换
System.out.println(userVo.getWeight()); // 输出100.50
格式日期
@Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
User user = new User();
user.setBirthday(new Date());
UserVo userVo = UserConverter.CONVERTER.toUserVo(user);
// userVo的birthday为String类型,类型不同会自动转换
System.out.println(userVo.getBirthday()); // 输出2024-08-17 19:05:05
来看下自动生成的代码
userVo.setWeight( new DecimalFormat( "#.00" ).format( user.getWeight() ) );
if ( user.getBirthday() != null ) {
userVo.setBirthday( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" ).format( user.getBirthday() ) );
}
userVo.setId( 111 );
2、进阶用法
2.1 属性里有复杂对象
@Data
public class Group {
private int id;
private String name;
private List<User> members;
}
@Data
public class GroupVo {
private int id;
private String name;
private List<UserVo> membersVo;
}
注意看下members和membersVo,它们里面存的对象是不一样的,mapstruct会自动解析它们之间的关系。
写法如下:
@Mapper
public interface GroupConverter {
GroupConverter CONVERTER = Mappers.getMapper(GroupConverter.class);
@Mapping(source = "members", target = "membersVo")
GroupVo toGroupVo(Group group);
@InheritInverseConfiguration
Group toGroup(GroupVo groupVo);
}
2.2 表达式
2.2.1 可以使用expression属性来调用代码(方法)
@Mapping(target = "nickName", expression = "java(user.getName().toUpperCase())")
UserVo toUserVo(User user);
将会生成如下代码:
userVo.setNickName( user.getName().toUpperCase() );
2.2.2 使用qualifiedByName调用方法
@Mapping(target = "nickName", source = "name", qualifiedByName = "toUpperCase")
UserVo toUserVo(User user);
@InheritInverseConfiguration
User toUser(UserVo userVo);
@Named("toUpperCase")
default String toUpperCase(String name) {
return name.toUpperCase();
}
如果使用了@InheritInverseConfiguration注解反向也会调用方法
生成代码如下:
public UserVo toUserVo(User user) {
if ( user == null ) {
return null;
}
UserVo userVo = new UserVo();
userVo.setNickName( toUpperCase( user.getName() ) );
.........
.....
return userVo;
}
@Override
public User toUser(UserVo userVo) {
if ( userVo == null ) {
return null;
}
User user = new User();
user.setName( toUpperCase( userVo.getNickName() ) );
.......
...
return user;
}
2.3 实现类加入spring容器
在接口上方使用如下注解
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
生成的方法会加上@Component
@Component
public class UserConverterImpl implements UserConverter { ... }
2.4 多个对象转一个对象
限定名称即可,如果target对象的某个属性在两个source中都有,必须指定从哪个取。比如user和order实例都有id属性,这个时候必须指定复制哪个实例的id
@Mapping(source = "user.id", target = "id")
@Mapping(source = "user.name", target = "nickName")
@Mapping(source = "order.orderNo", target = "orderNo")
UserVo toUserVo(User user, Order order);
参考官方示例:https://github.com/mapstruct/mapstruct-examples