mapstruct 使用与问题解决

mapstruct可以很好的解决DTO,VO,PO等等实体直接属性copy的问题,比BeanUtils.copyProperties()更好用。但是mapstruct与lombok会有版本冲突。

一。MapStruct怎么使用

1.MapStruct是用来做什么的?


现在有这么个场景,从数据库查询出来了一个user对象(包含id,用户名,密码,手机号,邮箱,角色这些字段)和一个对应的角色对象role(包含id,角色名,角色描述这些字段),现在在controller需要用到user对象的id,用户名,和角色对象的角色名三个属性。一种方式是直接把两个对象传递到controller层,但是这样会多出很多没用的属性。更通用的方式是需要用到的属性封装成一个类(DTO),通过传输这个类的实例来完成数据传输。
User.java

@AllArgsConstructor
@Data
public class User {
    private Long id;
    private String username;
    private String password;
    private String phoneNum;
    private String email;
    private Role role;
}

Role.java

@AllArgsConstructor
@Data
public class Role {
    private Long id;
    private String roleName;
    private String description;
}

UserRoleDto.java,这个类就是封装的类

@Data
public class UserRoleDto {
    /**
     * 用户id
     */
    private Long userId;
    /**
     * 用户名
     */
    private String name;
    /**
     * 角色名
     */
    private String roleName;
}

测试类,模拟将user对象转换成UserRoleDto对象

public class MainTest {
    User user = null;

    /**
     * 模拟从数据库中查出user对象
     */
    @Before
    public void before() {
       Role role  = new Role(2L, "administrator", "超级管理员");
       user  = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", role);
    }

    /**
     * 模拟把user对象转换成UserRoleDto对象
     */
    @Test
    public void test1() {
        UserRoleDto userRoleDto = new UserRoleDto();
        userRoleDto.setUserId(user.getId());
        userRoleDto.setName(user.getUsername());
        userRoleDto.setRoleName(user.getRole().getRoleName());
        System.out.println(userRoleDto);
    }
}

从上面代码可以看出,通过getter、setter的方式把一个对象属性值复制到另一个对象中去还是很麻烦的,尤其是当属性过多的时候。而MapStruct就是用于解决这种问题的。

2.使用MapStruct解决上述问题

这里我们沿用User.java、Role.java、UserRoleDto.java。
新建一个UserRoleMapper.java,这个来用来定义User.java、Role.java和UserRoleDto.java之间属性对应规则:

POM.xml引入依赖

<!-- bean属性转换工具 -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-jdk8</artifactId>
            <version>1.3.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.3.0.Final</version>
        </dependency>


UserRoleMapper.java
 

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
 *          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
 */
@Mapper
public interface UserRoleMapper {

    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方法都是public abstract的
     */
    UserRoleMapper INSTANCES = Mappers.getMapper(UserRoleMapper.class);

    /**
     * 这个方法就是用于实现对象属性复制的方法
     *
     * @Mapping 用来定义属性复制规则 source 指定源对象属性 target指定目标对象属性
     *
     * @param user 这个参数就是源对象,也就是需要被复制的对象
     * @return 返回的是目标对象,就是最终的结果对象
     */
    @Mappings({
            @Mapping(source = "id", target = "userId"),
            @Mapping(source = "username", target = "name"),
            @Mapping(source = "role.roleName", target = "roleName")
    })
    UserRoleDto toUserRoleDto(User user);

}

在测试类中测试:

public class MainTest {
    User user = null;

    /**
     * 模拟从数据库中查出user对象
     */
    @Before
    public void before() {
       Role role  = new Role(2L, "administrator", "超级管理员");
       user  = new User(1L, "zhangsan", "12345", "17677778888", "123@qq.com", role);
    }

    /**
     * 模拟通过MapStruct把user对象转换成UserRoleDto对象
     */
    @Test
    public void test2() {
        UserRoleDto userRoleDto = UserRoleMapper.INSTANCES.toUserRoleDto(user);
        System.out.println(userRoleDto);
    }
}

通过上面的例子可以看出,使用MapStruct方便许多。

3.添加默认方法

添加默认方法是为了这个类(接口)不只是为了做数据转换用的,也可以做一些其他的事。

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/**
 * @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
 *          在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
 */
@Mapper
public interface UserRoleMapper {

    /**
     * 获取该类自动生成的实现类的实例
     * 接口中的属性都是 public static final 的 方
<think>我们有两个问题需要解决:1)IDEA中测试包依赖配置问题;2)YAML文件中'Invalidchildelementinablockmapping'错误。下面分别给出解决方案。问题1:IDEA中测试包依赖配置问题在之前的回答中已经详细说明了如何配置测试包依赖(Maven和Gradle),但用户可能在实际操作中遇到了一些问题。这里再强调几个关键点和常见问题解决方案:1.确保依赖作用域(scope)正确:对于测试依赖,Maven中应使用`<scope>test</scope>`,Gradle中应使用`testImplementation`或`testCompile`(旧版本)。2.刷新依赖:在修改构建文件(pom.xml或build.gradle)后,必须刷新项目依赖。在IDEA中,可以通过Maven或Gradle工具窗口的刷新按钮完成。3.检查依赖是否下载成功:如果依赖下载失败,可能是因为仓库配置问题。可以尝试更换镜像仓库(如阿里云镜像)或检查网络连接。4.检查IDEA中的模块依赖:进入File->ProjectStructure->Modules,查看依赖是否被正确引入,作用域是否为Test。如果测试代码仍然无法识别(例如,无法识别@Test注解),请尝试以下步骤:-确保测试代码位于正确的目录下:Maven项目为`src/test/java`,Gradle项目为`src/test/java`(默认)。-检查IDEA是否将测试目录标记为TestSourcesRoot:右键点击测试目录->MarkDirectoryas->TestSourcesRoot。问题2:YAML文件中的'Invalidchildelementinablockmapping'错误这个错误通常是由于YAML语法错误引起的。YAML对缩进和格式非常敏感。下面是常见原因和解决方案:原因分析:1.缩进问题:YAML使用空格缩进(不能使用制表符),且同一级别的元素必须使用相同的缩进。2.不正确的嵌套:在块映射(使用键值对)中,如果值是一个新的块映射(子节点),则必须正确缩进。如果值是一个列表(序列),则要用`-`开头。3.使用了不合适的符号:比如在应该使用空格的地方用了冒号,或者缺少冒号等。解决方案(以SpringBoot的application.yml为例):假设有以下错误示例:```yamlserver:port:8080servlet:context-path:/apidatasource:#这个键和servlet是同一级,但错误地使用了相同的缩进,实际上应该servlet对齐url:jdbc:mysql://localhost:3306/mydbusername:root```正确写法应该是:```yamlserver:port:8080servlet:context-path:/apidatasource:#现在servlet同级,因为缩进相同url:jdbc:mysql://localhost:3306/mydbusername:root```或者,如果datasource应该是servlet的子节点,那么应该增加缩进(但这里显然不是,因为datasource通常不会在server.servlet下)。另一个常见错误是在使用列表时:错误示例:```yamlmyapp:servers:-name:server1address:192.168.1.1#这里应该name在同一层级,但缺少了缩进和破折号```正确写法:```yamlmyapp:servers:-name:server1address:192.168.1.1-name:server2address:192.168.1.2```注意:每个列表项用破折号(-)开始,然后后面跟一个空格,再跟键值对。同一项的键值对必须缩进到破折号相同的级别或更深的级别。在DockerCompose文件中也可能出现类似错误,如引用[2]中提到的:错误信息:`expected<blockend>,butfound'<blocksequencestart>'`这通常是因为在应该使用列表(以破折号开头)的地方没有正确使用,或者在开始列表时格式不正确。解决步骤:1.检查提示出错的行(根据错误信息给出的行号和列号,比如第4行第5列,第11行第8列)。2.使用在线YAML校验工具(如YAMLLint)帮助定位错误。3.确保整个文件中的缩进一致(推荐使用2个空格作为一级缩进)。示例:DockerCompose文件错误(引用[2])错误信息指向两个地方,可能是这样的:```yamlversion:'3'services:#块映射开始web:image:nginxports:#这里应该是一个列表-"8000:8000"#正确,但是假设第11行有错误redis:#这个服务定义应该在services下,web同级image:redisports:#假设第11行是ports,第11行第8列是ports键值对的位置-"6379:6379"volumes:#假设在volumes这里出现了错误-./data:/data#正确写法#错误示例:如果volumes写成下面这样(少了序列符号-),就会出错volumes:./data:/data#错误:volumes的值应该是一个列表,而这里直接写字符串,不符合YAML语法```正确写法中,volumes的值应该是一个列表,每个元素是一个字符串(用破折号开头)。如果用户在使用Redis配置时遇到问题,可以参考引用[3]中的建议,例如配置密码和数据持久化:```yamlversion:'3'services:redis:image:redis:6.2.5#指定版本号command:redis-server--requirepassyourpassword#通过命令设置密码volumes:-./data:/data#持久化数据ports:-"6379:6379"```总结:对于YAML错误,仔细检查缩进、列表项的格式(使用`-`)以及键值对的冒号后面是否有空格。由于用户同时提到了这两个问题,我们分别给出解决方案。如果用户是在测试SpringBoot项目时遇到YAML配置错误,那么需要检查`src/main/resources/application.yml`文件中的语法。相关问题:我们可以根据用户的问题生成以下相关问题:§§相关问题§§1.如何在IntelliJIDEA中调试测试代码的依赖加载过程?2.多模块项目中如何统一管理测试依赖版本?3.如何配置TestNG替代JUnit作为测试框架?4.如何用IDEA分析项目中的依赖冲突?5.如何避免SpringBoot中YAML配置文件的常见语法错误?6.DockerCompose文件中的YAML格式有哪些注意事项?</think> ### 问题一:IDEA测试包依赖配置问题解决方案 #### 1. **验证测试作用域配置** - **Maven项目**:检查`pom.xml`是否正确定义`<scope>test</scope>` ```xml <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.9.2</version> <scope>test</scope> <!-- 必须声明为test作用域 --> </dependency> ``` - **Gradle项目**:确认使用`testImplementation`作用域 ```gradle dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' } ``` #### 2. **IDEA依赖刷新操作 | 操作步骤 | 说明 | |----------|------| | **Maven** | 右侧Maven工具栏 → 刷新按钮(🔄)或执行`mvn clean install` | | **Gradle** | Gradle工具栏 → 刷新图标 或执行`./gradlew --refresh-dependencies` | #### 3. **常见问题排查 - **测试类不可见**: ```markdown 1. 检查测试目录标记:右键`src/test/java` → *Mark Directory as* → **Test Sources Root** 2. 验证作用域:*File* > *Project Structure* > *Modules* > **Dependencies** → 确认Scope为`Test` ``` - **依赖冲突**: 执行依赖树分析: ```bash # Maven mvn dependency:tree # Gradle gradle dependencies ``` 在冲突依赖中使用`<exclusions>`(Maven)或`exclude`(Gradle)解决 --- ### 问题二:YAML文件`Invalid child element in a block mapping`错误解决方案 #### 1. **核心问题诊断 此错误通常由**缩进错误**或**格式冲突**引起: - **根本原因**:YAML要求严格对齐(必须使用空格缩进,不能混合制表符) - **典型场景**:在块映射(`key: value`)中错误嵌套了序列(`- item`)或错误缩进层级[^1][^2] #### 2. **修复步骤(以Spring Boot配置为例) **错误示例**: ```yaml server: port: 8080 servlet: # 错误:应port同级但未对齐 context-path: /api data: # 错误:缺少缩进定义子元素 - url: jdbc:mysql://localhost:3306/mydb # 错误:序列项需单独缩进 ``` **正确写法**: ```yaml server: port: 8080 servlet: # port保持相同缩进 context-path: /api datasource: # 正确块映射 url: jdbc:mysql://localhost:3306/mydb # 作为datasource的子属性 ``` #### 3. **关键验证工具 - **在线校验**:使用[yamllint.com](https://www.yamllint.com/)验证语法 - **IDEA内置检查**: *右键YAML文件* → **Validate YAML** → 自动定位错误行 - **缩进规范**:建议**每级2个空格**(禁止使用Tab) #### 4. **Docker Compose文件特例处理 当出现类似错误时(如引用[2]): ```yaml services: redis: image: redis:7.0 volumes: ./data:/data # 错误:值应为列表格式 ``` **修复方案**: ```yaml services: redis: image: redis:7.0 volumes: # 正确序列格式 - ./data:/data command: redis-server --requirepass yourpass # 命令行参数需单独声明[^3] ``` --- ### ⚠️ 综合注意事项 1. **IDEAYAML联动**: 在`application.yml`修改后,使用`Ctrl+Shift+F10`重新加载配置 2. **依赖配置关联**: Spring Boot测试依赖需同步配置YAML测试环境: ```xml <!-- Maven示例 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> ``` --- ### 相关问题延伸 1. 如何解决Spring Boot测试中的`@Value`注入失败问题? 2. YAML配置多环境时如何避免语法错误(如`spring.profiles`的使用)? 3. 如何在Docker Compose中正确配置Redis持久化以避免数据丢失?[^3] 4. IDEA如何自动格式化YAML文件保持正确缩进? 5. 多模块项目中如何共享YAML配置文件?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值