MapStruct 使用总结

前置条件

maven 引入依赖

 <properties>
        <java.version>11</java.version>
        <java.version>1.8</java.version>
        <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.2</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>

        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.4.2.Final</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>


        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
       
    </dependencies>

1-lombok(若是使用lombok,不仅仅是@Data,还需要别的注解,具体google),2-mapstruct ,3-hutool-all


public class Student {

    private String name;
    private String sex;
    private Integer age;
    // 对应person -> tall
    private Long height;
    private Integer rank;
    /** long 形式的时间格式 */
   private Long time;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Long getHeight() {
        return height;
    }

    public void setHeight(Long height) {
        this.height = height;
    }

    public Integer getRank() {
        return rank;
    }

    public void setRank(Integer rank) {
        this.rank = rank;
    }

    public Long getTime() {
        return time;
    }

    public void setTime(Long time) {
        this.time = time;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                ", age=" + age +
                ", height=" + height +
                ", rank=" + rank +
                ", time=" + time +
                '}';
    }
}


public class Person {
    private String name;
    private String sex;
    private Integer age;
    private String tall;
    private String time;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getTall() {
        return tall;
    }

    public void setTall(String tall) {
        this.tall = tall;
    }

    public String getTime() {
        return time;
    }

    public void setTime(String time) {
        this.time = time;
    }
}

写MapStruct 接口

注意点
1- student - height 与 person-tall 要相互复制的。类型和名字都不同
2-student-time 与 person-time 名字相同,类型不同
结论
1-对于 string 类型与 基本类型之间,若名字相同,类型不同,可直接转化(不要手动干预)
2-如时间,string与long的转化需要定义方法


import cn.hutool.core.date.DateUtil;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.Named;
import org.mapstruct.factory.Mappers;

import java.sql.Time;

/*    @Mapping(source = "time",target = "time",expression = "java(StructHelper.DateTimeStrToTimeMillis(person.getTime()))"*/
@Mapper(componentModel = "spring", uses = StructHelper.class)
public interface MapStructTest {
    //    MapStructTest INSTANCE = Mappers.getMapper(MapStructTest.class);
    @Mappings({
            @Mapping(source = "tall", target = "height"),
            @Mapping(source = "time", target = "time", qualifiedByName = StructConstant.DATE_TIME_STR_TO_TIME_MILLIS)
    })
    Student personToStudent(Person person);


//    @Named(value = "dateTimeStrToTimeMillis")
//    default  Long DateTimeStrToTimeMillis(String dataTimeStr) {
//
//        return DateUtil.parseDateTime(dataTimeStr).getTime();
//    }
}

方式一:expression
缺点:需要写 全限定类型。不好维护

@Mapping(source = "time",target = "time",expression = "java(StructHelper.DateTimeStrToTimeMillis(person.getTime()))

**方式二:qualifiedByName **
优点:维护性比较高。
我这里定义了常量。方便 Ctrl+鼠标左键,进行点击。

若定义转换方法不多的话,可以直接写在这个接口里。然后常量类也省去了。

自定义方法


@Component
@Named(value = "StructHelper")
public class StructHelper {

    /**
     * 时间搓转换程日期格式
     * @param timeMillis
     * @return
     */
    @Named(value = StructConstant.TIME_MILLIS_TO_DATE_STR)
    public static String timeMillisToDateStr(Long timeMillis) {

        return DateUtil.formatDate(DateUtil.date(timeMillis));
    }

    /**
     * 时间搓转换程日期时间格式
     * @param timeMillis
     * @return
     */
    @Named(value = StructConstant.TIME_MILLIS_TO_DATE_TIME_STR)
    public static String timeMillisToDateTimeStr(Long timeMillis) {

        return DateUtil.formatDateTime(DateUtil.date(timeMillis));
    }

    /**
     * 日期时间格式转换程时间搓
     * @param dataStr
     * @return
     */
    @Named(value = StructConstant.DATE_STR_TO_TIME_MILLIS)
    public static Long DateStrToTimeMillis(String dataStr) {

        return DateUtil.parseDateTime(dataStr).getTime();
    }

    /**
     * 日期时间格式转换程时间搓
     * @param dataTimeStr
     * @return
     */
    @Named(value = StructConstant.DATE_TIME_STR_TO_TIME_MILLIS)
//    @Named(value = "dateTimeStrToTimeMillis")
    public static Long DateTimeStrToTimeMillis(String dataTimeStr) {

        return DateUtil.parseDateTime(dataTimeStr).getTime();
    }
}

注意点
在这里插入图片描述

使用

方式一:apStructTest INSTANCE = Mappers.getMapper(MapStructTest.class);
方式二:直接注入,写的接口,MapStructTest
实际两者一样,获得一个对象,然后调用方法

原理

第一步:执行下
在这里插入图片描述
第二步:
在这里插入图片描述
第三步:

  @Override
    public InvoiceApplyVo entityToInvoiceApplyVo(InvoiceApply invoiceApply) {
        if ( invoiceApply == null ) {
            return null;
        }

        InvoiceApplyVo invoiceApplyVo = new InvoiceApplyVo();

        invoiceApplyVo.setAuditTime( structHelper.timeMillisToDateTimeStr( invoiceApply.getAuditTime() ) );
        invoiceApplyVo.setId( invoiceApply.getId() );
        invoiceApplyVo.setInvoiceContent( invoiceApply.getInvoiceContent() );
        invoiceApplyVo.setInvoiceRiseName( invoiceApply.getInvoiceRiseName() );
        invoiceApplyVo.setAdminName( invoiceApply.getAdminName() );
        invoiceApplyVo.setStatus( invoiceApply.getStatus() );

        return invoiceApplyVo;
    }

上面对类型的转换,使用的自定义的函数调用

复杂类型

list --> list

    /**
     * List<InvoiceApply>--> List<InvoiceApplyVo>
     *
     * @param invoiceApplies
     * @return
     */
    List<InvoiceApplyVo> toInvoiceApplyVo(List<InvoiceApply> invoiceApplies);

看自动生成的接口实现


    @Override
    public List<InvoiceApplyVo> toInvoiceApplyVo(List<InvoiceApply> invoiceApplies) {
        if ( invoiceApplies == null ) {
            return null;
        }

        List<InvoiceApplyVo> list = new ArrayList<InvoiceApplyVo>( invoiceApplies.size() );
        for ( InvoiceApply invoiceApply : invoiceApplies ) {
            list.add( entityToInvoiceApplyVo( invoiceApply ) );
        }

        return list;
    }

这里,会自动调用单个的,然后for循环。

总结

mapStruct 实际上是比较好用的,可能前期写的时候,可能比较繁琐。
但是在 Dto、entity,vo,转换的时候,可以省事一些。

lombok+MapStruct兼容使用

若版本不对应的话,会导致这两个玩意不能兼容
出现这样

    public XXXVO toXXXVO(XXXModel model) {
        if ( model == null ) {
            return null;
        }
 
        XXXVO xxxVO = new XXXVO();
 
        return xxxVO;
    }

答案是:mapstruct-1.3.0.Final,对应lombok-1.18.10 就可以正常使用了!done.

常见错误

Error:(38, 13) java: Can’t map property “Integer autoTakeTime” to “Integer autoTakeTime”. Consider to declare/implement a mapping method: “Integer map(Integer value)”.
检查你 自定义转换方法
比如

    @Named(value = StructConstant.TIME_MILLIS_TO_DATE_TIME_STR)
    public String timeMillisToDateTimeStr(Long timeMillis) {
        if (ValidateUtil.isEmpty(timeMillis)){
            return "";
        }
        return DateUtil.formatDateTime(DateUtil.date(timeMillis));
    }

实际上。自定义方法是将long 转为 String。但是 target对象使用Interger接的。所有抛出了异常。

### MapStruct 的配置与映射器生成 #### 工具简介 MapStruct 是一种基于注解处理器的 Java 映射框架,能够自动生成高效的对象到对象映射代码。相比传统的手动编码方式或依赖反射的方式,它不仅减少了开发工作量,还显著提升了性能[^1]。 --- #### Maven/Gradle 配置 为了使用 MapStruct,在项目中需要引入相应的依赖项以及编译插件: 对于 **Maven** 用户,需在 `pom.xml` 文件中添加以下内容: ```xml <dependencies> <!-- MapStruct API --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.3.Final</version> <!-- 版本号可根据需求调整 --> </dependency> <!-- MapStruct Processor (用于生成实现类) --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.3.Final</version> <scope>provided</scope> </dependency> </dependencies> <!-- 确保启用 annotationProcessor 支持 --> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.3.Final</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build> ``` 对于 **Gradle** 用户,则可以这样配置: ```gradle implementation 'org.mapstruct:mapstruct:1.5.3.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.3.Final' // 如果使用 Kotlin DSL dependencies { implementation("org.mapstruct:mapstruct:1.5.3.Final") annotationProcessor("org.mapstruct:mapstruct-processor:1.5.3.Final") } ``` --- #### 定义 Mapper 接口 创建一个接口作为映射器,并标注 `@Mapper` 注解。例如,假设有一个实体类 `User` 和对应的 DTO 类 `UserDTO`,可以通过如下方式定义它们之间的映射关系: ```java import org.mapstruct.Mapper; import org.mapstruct.Mapping; @Mapper(componentModel = "spring") // 将 mapper 实现注册为 Spring Bean public interface UserMapper { @Mapping(source = "name", target = "userName") // 自定义字段映射逻辑 UserDTO userToUserDTO(User user); List<UserDTO> usersToUserDTOs(List<User> users); } ``` 在此基础上,如果某些字段不需要被映射,可利用 `@Mapping(ignore = true)` 来忽略这些字段;而对于更复杂的场景(如嵌套对象),则可通过额外的方法完成子对象的映射。 --- #### 处理集合和特殊数据结构 针对集合类型的映射,通常会借助于 `@IterableMapping` 或者直接声明列表映射方法。比如下面的例子展示了如何处理包含空值的情况: ```java @Mapper public interface UserMapper { @IterableMapping(nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT) // 设置空值策略 List<UserDTO> toUserDTOList(List<User> userList); } ``` 另外,当涉及到键值对形式的数据时,可以采用 `@MapMapping` 注解来指定目标类型及其内部组件的具体化规则[^2]。 --- #### 自动生成的映射器实现 一旦完成了上述步骤,运行构建命令之后,MapStruct 即会在后台生成具体的实现类文件。此过程完全透明化,开发者无需关心底层细节即可享受高效便捷的服务支持[^3]。 --- ### 示例总结 以下是完整的示例代码片段展示整个流程: ```java @Entity class User { private Long id; private String name; } @Data class UserDTO { private Long userId; private String userName; } @Mapper(componentModel = "spring") interface UserMapper { @Mapping(source = "id", target = "userId") @Mapping(source = "name", target = "userName") UserDTO map(User source); default List<UserDTO> mapAll(Iterable<? extends User> sources) { return StreamSupport.stream(sources.spliterator(), false) .map(this::map) .collect(Collectors.toList()); } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值