使用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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值