使用mapstruct实现BeanCopy

MapStruct是一个开源的Java映射框架,它可以帮助开发者在不同类型的Java对象之间进行无缝转换。通过使用注解配置映射规则,MapStruct可以自动生成高效的映射代码,减少手动编写转换逻辑的工作量。它支持在编译时生成映射代码,因此可以提供更好的性能和类型安全性。总的来说,MapStruct使Java对象之间的转换变得更加简单和高效。

1.使用MapStruct替换BeanUtils

在处理一般简单场景时,可以选择使用BeanUtils、Spring官方工具或其他第三方工具,它们功能相似。然而,对于复杂字段映射,这些工具可能不够强大,并且在进行批量转换时,效率也会受到影响。这时候,MapStruct就能很好地解决这两个问题。在编译时,MapStruct会自动生成映射代码,保证高性能、提前发现问题并让开发人员能够进行彻底的错误检查。

2.集成MapStruct

2.1.首先需要依赖他的相关的jar包
<!-- 引入mapstruct -->
<dependency>
	<groupId>org.mapstruct</groupId>
	<artifactId>mapstruct</artifactId>
	<version>1.4.2.Final</version>
</dependency>

因为MapStruct需要在编译器生成转换代码,所以需要在maven-compiler-plugin插件中配置上对mapstruct-processor的引用,由于项目中已经使用了lombok和fluent-mybatis,
所以这两个也需要纳入管理,如果还有其他需要在编译器生成转换代码的组件,也需要纳入管理(可以保证编译的顺序)

注意:官网有坑,lombok一定要放在最前面,让其先生成get/set方法,否则mapstruct编译会找不到属性的get/set方法

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-compiler-plugin</artifactId>
	<version>3.8.1</version>
	<configuration>
		<source>1.8</source>
		<target>1.8</target>
		<encoding>utf-8</encoding>
		<!-- 管理自动生成代码,会按Path先后先顺序生成lombok放在前面,否则mapstruct使用时会找不到get方法 -->
		<annotationProcessorPaths>
			<path>
				<groupId>org.projectlombok</groupId>
				<artifactId>lombok</artifactId>
				<version>1.18.16</version>
			</path>
			<path>
				<groupId>org.mapstruct</groupId>
				<artifactId>mapstruct-processor</artifactId>
				<version>1.4.2.Final</version>
			</path>

			<path>
				<groupId>com.github.atool</groupId>
				<artifactId>fluent-mybatis-processor</artifactId>
				<version>1.6.8</version>
			</path>
			<!-- other annotation processors -->
		</annotationProcessorPaths>
	</configuration>
</plugin>
2.2.我们需要定义一个做映射的接口(实体类和DTO代码省略)
/**
 * MapStruct实体类和DTO转换 类名不能以Mapper结尾,否则和fluent-mybatis冲突
 */
@Mapper
public interface MapStruct {
    MapStruct INSTANCE = Mappers.getMapper(MapStruct.class);

    /**
     * 将ApiGroupEntity转成ApiGroupDto
     * @param entity
     * @return
     */
    @Mappings({
            @Mapping(source = "id", target = "id"),
            @Mapping(source = "name", target = "name"),
            @Mapping(source = "groupRemark", target = "groupRemark")
    })
    ApiGroupDto convertEntityToDto(ApiGroupEntity entity);
}

编译成功后,会在target下自动生成对应的实现类MapStructImpl

package com.cnhqd.scap.dataStation.mapper.mapstruct;

import com.cnhqd.scap.dataStation.dto.api.ApiGroupDto;
import com.cnhqd.scap.dataStation.entity.api.ApiGroupEntity;

public class MapStructImpl implements MapStruct {
    public MapStructImpl() {
    }

    public ApiGroupDto convertEntityToDto(ApiGroupEntity entity) {
        if (entity == null) {
            return null;
        } else {
            ApiGroupDto apiGroupDto = new ApiGroupDto();
            apiGroupDto.setId(entity.getId());
            apiGroupDto.setName(entity.getName());
            apiGroupDto.setGroupRemark(entity.getGroupRemark());
            apiGroupDto.setDomainInfo(entity.getDomainInfo());
            apiGroupDto.setRelProjectId(entity.getRelProjectId());
            apiGroupDto.setCreatePerson(entity.getCreatePerson());
            return apiGroupDto;
        }
    }
}
2.3.测试
public static void main(String[] args) {
    ApiGroupEntity entity = new ApiGroupEntity();
    entity.setId("123");
    entity.setGroupRemark("789");
    entity.setName("API分组");
    ApiGroupDto apiGroupDto = MapStruct.INSTANCE.convertEntityToDto(entity);
    System.out.println(apiGroupDto);
}

输出结果:

ApiGroupDto(id=123, name=API分组, groupRemark=789, domainInfo=null, relProjectId=null, createPerson=null)

Process finished with exit code 0

3.MapStruct的使用方法

MapStruct使用时只需使用映射类获取实例,调用对应方法即可

MapStruct.INSTANCE.convertEntityToDto(entity);

MapStruct支持属性名称不一致,属性类型不一致和多对一等多种方式,下面以简单的示例来展开

3.1属性名称不一致

需要转换的两个类属性名称不一致

/**
 * user类
 */
@Data
@AllArgsConstructor
public class User {
    private String id;
    private String name;
    private int age;
}
/**
 * userDto
 */
@Data
public class UserDto {
    private String userId;
    private String userName;
    private int userAge;
}

映射方法

@Mappings({
       @Mapping(source = "id", target = "userId"),
       @Mapping(source = "name", target = "userName"),
       @Mapping(source = "age", target = "userAge")
})
UserDto convertDto(User entity);

测试

public static void main(String[] args) {
    User user = new User("123", "张三", 25);
    UserDto userDto = EntityConvertDtoMapper.INSTANCE.convertDto(user);
    System.out.println(userDto);
}

输出结果:

UserDto(userId=123, userName=张三, userAge=25)
3.2属性类型不一致

需要转换的两个类属性类型不一致User的id为long,UserDto为String

@Data
@AllArgsConstructor
public class User {
    private long id;
    private String name;
    private int age;
}
@Data
public class UserDto {
    private String id;
    private String name;
    private int age;
}

映射方法
通过expression属性指定java表达式,表达式中的方法可以自定义。本例用String.valueOf()

@Mappings({
       @Mapping(target = "id", expression = "java(java.lang.String.valueOf(entity.getId()))")
})
UserDto convertDto(User entity);

测试

public static void main(String[] args) {
    User user = new User(123, "张三", 25);
    UserDto userDto = EntityConvertDtoMapper.INSTANCE.convertDto(user);
    System.out.println(userDto);
}

结果:

UserDto(id=123, name=张三, age=25)
3.3多对一

两个实体类转成一个Dto

@Data
@AllArgsConstructor
public class User {
    private long id;
    private String name;
    private int age;
}

@Data
@AllArgsConstructor
public class UserInfo {
    private long id;
    private String address;
    private String position;
}

@Data
public class UserDto {
    private String id;
    private String name;
    private int age;
    private String address;
    private String position;
}

映射方法

@Mappings({
        @Mapping(source = "user.id", target = "id"),
        @Mapping(source = "user.name", target = "name"),
        @Mapping(source = "user.age", target = "age"),
        @Mapping(source = "info.address", target = "address"),
        @Mapping(source = "info.position", target = "position")
})
UserDto convertDto(User user, UserInfo info);

测试:

public static void main(String[] args) {
    User user = new User(123, "张三", 25);
    UserInfo info = new UserInfo(123, "西安市", "产品经理");
    UserDto userDto = EntityConvertDtoMapper.INSTANCE.convertDto(user, info);
    System.out.println(userDto);
}

结果:UserDto(id=123, name=张三, age=25, address=西安市, position=产品经理)

3.4批量copy

一个Mappings映射,可以对应多个转换方法,例如:

@Mappings({
        @Mapping(source = "id", target = "userId"),
        @Mapping(source = "name", target = "userName"),
        @Mapping(source = "age", target = "userAge")
})
// 单条数据copy
UserDto convertDto(User entity);

// 批量copy
List<UserDto> convertListDto(List<User> entity);

如有问题,欢迎评论、指正!

4 和spring集成
@Mapper(componentModel = "spring")
public interface DataQualityJobCalculateDelayInfoConverter {

    /**
     * entity to dto
     * @param list
     * @return
     */
    List<DataQualityJobCalculateDelayInfoDto> toDtoList(List<DataQualityJobCalculateDelayInfo> list);

    DataQualityJobCalculateDelayInfoDto toDto(DataQualityJobCalculateDelayInfo info);

    DataQualityJobCalculateDelayInfo toEntity(DataQualityJobCalculateDelayInfoDto info);
}

在Spring中的使用

@Autowired
private DataQualityJobCalculateDelayInfoConverter converter;
<think> 首先,用户的问题是基于提供的引用,了解copyBean的使用方法、原理和相关工具。引用中提到了几种Bean映射工具,如Spring的BeanUtils.copyProperties、MapStruct等。 关键引用: - 引用[1]:描述了在项目中需要使用不同后缀的实体bean(如DO、MO、VO),需要在它们之间复制数据。提到了使用set/get方法、constructor或Spring的工具类,通过Java反射实现。 - 引用[2]:介绍了几种Bean映射工具,强调了Spring的BeanUtils.copyProperties优于Apache的BeanUtils,在性能和操作上更好。 - 引用[3]:讨论了Bean copy的实战,包括不同工具的性能比较,指出Apache BeanUtils性能差,而MapStruct性能好且使用方便。 用户询问copyBean的使用、原理及相关工具介绍。我需要基于这些引用构建一个清晰的回答。 系统指令: - 所有行内数学表达式必须用$...$格式,但这里涉及Java编程,没有数学表达式,所以可能不需要。 - 独立公式用$$...$$,同样不适用。 - LaTeX语法正确:不适用。 - 使用中文回答。 - 生成相关问题:在回答末尾需要添加相关问题。 - 回答中引用的段落末尾自然地添加引用标识,如[^1]。 回答结构应该清晰: 1. 介绍copyBean的概念和场景。 2. 解释原理:基于Java反射。 3. 使用方法:如何使用工具类如Spring的BeanUtils。 4. 相关工具介绍:Spring BeanUtils, MapStruct等。 5. 性能比较和推荐。 6. 结尾添加相关问题。 引用标识: - 当引用特定内容时,在段落末尾添加[^1], [^2], [^3]。 生成相关问题: - 在回答结束时,添加几个相关问题。 大纲: 1. **引言**:解释copyBean是什么,为什么需要它(基于引用[1])。 2. **原理**:基于Java反射(参考引用[1]和[2])。 3. **使用方法**:以Spring BeanUtils为例(参考引用[2])。 4. **相关工具介绍**: - Spring BeanUtils - Apache BeanUtils(不推荐) - MapStruct(高性能) - 其他(参考引用[3]) 5. **性能比较和推荐**:基于引用[3],强调MapStruct的性能优势。 6. **总结**。 7. **相关问题**。 确保语言流畅、专业。 </think> ### copyBean的使用、原理及相关工具介绍 在Java开发中,特别是在分层架构(如数据库层、业务层、前端交互层)中,实体Bean(如DO、MO、VO)经常需要进行数据复制(copyBean)。这避免了手动编写繁琐的set/get方法或构造函数,提升开发效率和代码可维护性。以下基于引用内容,详细介绍使用、原理和相关工具。 #### 1. **copyBean的使用方法** copyBean的核心是使用工具类实现Bean属性值的自动复制。常见工具如Spring的`BeanUtils.copyProperties`,适用于不同后缀的实体类(如DO到VO的转换)。 - **基本用法示例(以Spring BeanUtils为例)**: ```java import org.springframework.beans.BeanUtils; public class BeanCopyExample { public static void main(String[] args) { // 源Bean(如数据库实体DO) UserDO userDO = new UserDO(); userDO.setId(1); userDO.setName("Alice"); // 目标Bean(如前端交互实体VO) UserVO userVO = new UserVO(); // 使用BeanUtils复制属性(需确保属性名一致) BeanUtils.copyProperties(userDO, userVO); // 验证复制结果 System.out.println(userVO.getName()); // 输出: Alice } } ``` - **注意事项**: - 源Bean和目标Bean的属性名必须相同,否则属性不会被复制。 - 支持基本数据类型和复杂对象(如嵌套Bean),但需注意深拷贝与浅拷贝的区别。 - 适用于大部分场景,如数据库实体到传输实体的转换[^1]。 #### 2. **copyBean的原理** copyBean的实现基于Java反射机制。工具类自动扫描Bean的字段,通过反射调用目标Bean的setter方法设值注入。引用[1]中提到,Spring的工具类就是利用反射,根据目标Bean的字段名匹配源Bean的getter方法获取值,再调用目标Bean的setter方法赋值[^1]。 - **原理分解**: 1. **反射获取属性信息**:工具类使用`Class.getDeclaredFields()`获取Bean的所有字段。 2. **值复制逻辑**:遍历字段名,调用源Bean的`getXxx()`方法获取值,再调用目标Bean的`setXxx(value)`方法赋值。 3. **异常处理**:处理类型不匹配或字段不存在的情况(如忽略不兼容属性)。 - 例如,Spring的`BeanUtils`内部通过`org.springframework.beans.BeanWrapper`实现反射操作,确保高效和灵活[^2]。 这种方式避免了手动复制代码,但反射操作可能影响性能,尤其是在高频调用场景[^3]。 #### 3. **相关工具介绍** 多种工具可用于Bean复制,引用[2]和[3]对比了它们的性能和实用性: - **Spring BeanUtils**: - **特点**:官方工具类(`org.springframework.beans.BeanUtils.copyProperties`),性能优于Apache版本,操作简便。 - **适用场景**:小型项目或快速开发,属性名一致时效率高。但反射机制在高并发下可能成为瓶颈[^2]。 - **Apache BeanUtils**: - **特点**:Apache Commons的工具(`org.apache.commons.beanutils.BeanUtils.copyProperties`),功能类似Spring。 - **缺点**:性能较差(引用[3]指出其压测数据最差),且易引发反序列化问题(如Dubbo框架中)。不推荐使用[^3]。 - **MapStruct**: - **特点**:高性能映射工具(基于代码生成而非反射),使用时需定义Mapper接口,编译期生成实现类。 - **优点**:性能接近手动set/get(无反射开销),代码清晰(如IDE支持错误检查)。引用[3]强调其为“对象映射神器”,适合复杂对象复制[^3]。 - **示例**: ```java @Mapper public interface UserMapper { UserMapper INSTANCE = Mappers.getMapper(UserMapper.class); UserVO toVO(UserDO userDO); } // 使用: UserVO userVO = UserMapper.INSTANCE.toVO(userDO); ``` - **其他工具**: - **Dozer**:支持复杂映射(如不同类型转换),但配置繁琐。 - **Orika**:类似MapStruct,性能较好,但学习曲线较陡。 - **性能比较**:引用[3]总结,MapStruct和Orika在压测中表现最佳,而Apache BeanUtils应避免[^3]。 #### 4. **推荐与总结** - **工具选择**: - 简单场景:优先使用Spring BeanUtils(易用、集成Spring生态)。 - 高性能需求:选用MapStruct(编译期优化,避免反射开销)。 - 避免Apache BeanUtils:性能低下且易出错。 - **最佳实践**: - 确保源和目标Bean属性名一致。 - 复杂对象复制时,测试性能和兼容性(如深拷贝问题)。 - MapStruct等工具可减少代码量,提升可维护性[^3]。 copyBean能显著提升开发效率,但工具选择需结合项目需求和性能考量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值