简体   繁体   English

未映射的目标属性和 mapstruct 使用 null 参数调用构造函数

[英]unmapped target property and mapstruct calls constructor with null parameter

I'm exploring mapstruct to map JPA entities and DTO objects.我正在探索 map JPA 实体和 DTO 对象的 mapstruct。 Entities and DTOs have abstract base classes that contain id and version fields that I'd like to keep private so that they can not be modified (public getter, no setter for both types).实体和 DTO 具有包含 id 和 version 字段的抽象基类,我希望它们保持私有,以便它们不能被修改(公共 getter,两种类型都没有 setter)。 I made a most simple reproducer to demonstrate the idea.我做了一个最简单的复制器来演示这个想法。 Abstract Base class has a private field name. Abstract Base class 有一个私有字段名称。 To copy the field values back and forth Base defines a constructor that has a Base parameter.为了来回复制字段值,Base 定义了一个具有 Base 参数的构造函数。 The constructor picks the private field from the parameter and assigns it to it's own private field:构造函数从参数中选择私有字段并将其分配给它自己的私有字段:

package de.ruu.lab.map.read_only_field_in_base;

public abstract class Base
{
    private String name;

    public    Base(String name) { this.name = name; }
    protected Base(Base source) { name = source.name; }

    public String getName() { return name; }
}

These are the subclasses of Base:这些是 Base 的子类:

package de.ruu.lab.map.read_only_field_in_base;

import de.ruu.lab.map.read_only_field_in_base.SimpleMapper.Default;

public class Source extends Base
{
    public Source(String name) { super(name); }
    @Default
    public Source(Base   base) { super(base); }
}
package de.ruu.lab.map.read_only_field_in_base;

import de.ruu.lab.map.read_only_field_in_base.SimpleMapper.Default;

public class Target extends Base
{
    public Target(String name) { super(name); }
    @Default
    public Target(Base   base) { super(base); }
}

I have to annotate the default constructor to resolve constructor ambiguity for mapstruct.我必须注释默认构造函数以解决 mapstruct 的构造函数歧义。 The mapper looks like this:映射器如下所示:

package de.ruu.lab.map.read_only_field_in_base;

import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.RetentionPolicy.CLASS;

import java.lang.annotation.Retention;

import org.mapstruct.Mapper;
import org.mapstruct.Qualifier;
import org.mapstruct.factory.Mappers;

@Mapper
public interface SimpleMapper
{
    SimpleMapper INSTANCE = Mappers.getMapper(SimpleMapper.class);

    Source toSource(Target target);
    Target toTarget(Source source);

    @Qualifier // make sure that this is the MapStruct qualifier annotation
    @java.lang.annotation.Target(CONSTRUCTOR)
    @Retention(CLASS)
    public @interface Default { }
}

The first problem is that mapstruct warns that there is an unmapped target property "base".第一个问题是 mapstruct 警告说有一个未映射的目标属性“base”。 What does that mean?这意味着什么? Which target property is not mapped?哪个目标属性未映射? Wouldn't it be possible to print the name of the property in the warning?难道不能在警告中打印属性的名称吗? I use eclipse as IDE, maybe the behaviour is different with other tools?我使用 eclipse 作为 IDE,也许其他工具的行为不同?

I tried annotating the mapping methods with我尝试用注释映射方法

    @Mapping(target="name", ignore = true)

but that does not let the warning disappear.但这不会让警告消失。

Because mapstruct just makes a warning I hoped everything would be ok and I created a tiny test class:因为 mapstruct 只是发出警告,我希望一切都会好起来,我创建了一个小测试 class:

package de.ruu.lab.map.read_only_field_in_base;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;

import org.junit.jupiter.api.Test;

class SimpleMapperTest
{
    @Test void shouldMapSourceToTarget()
    {
        Source source = new Source("map me");
        Target target = SimpleMapper.INSTANCE.toTarget(source);
        assertThat(target.getName(), is(source.getName()));
    }

    @Test void shouldMapTargetToSource()
    {
        Target target = new Target("map me");
        Source source = SimpleMapper.INSTANCE.toSource(target);
        assertThat(source.getName(), is(target.getName()));
    }
}

Both tests fail with a NPE because of some strange code mapstruct generated:由于生成了一些奇怪的代码 mapstruct,这两个测试都因 NPE 而失败:

package de.ruu.lab.map.read_only_field_in_base;

import javax.annotation.processing.Generated;

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2022-09-18T11:08:20+0200",
    comments = "version: 1.5.2.Final, compiler: Eclipse JDT (IDE) 1.4.200.v20220802-0458, environment: Java 17.0.2 (GraalVM Community)"
)
public class SimpleMapperImpl implements SimpleMapper {

    @Override
    public Source toSource(Target target) {
        if ( target == null ) {
            return null;
        }

        Base base = null;

        Source source = new Source( base );

        return source;
    }

    @Override
    public Target toTarget(Source source) {
        if ( source == null ) {
            return null;
        }

        Base base = null;

        Target target = new Target( base );

        return target;
    }
}

Obviously code like this causes the NPE:显然这样的代码会导致 NPE:

        Base base = null;

        Source source = new Source( base );

IMO this would be correct ("target" is the name of the method's parameter): IMO 这是正确的(“目标”是方法参数的名称):

        Source source = new Source( target );

Maybe this can be solved in an upcoming version.也许这可以在即将发布的版本中解决。 Meanwhile, is there any recommendation how to deal with this now?同时,现在有什么建议如何处理这个问题吗?

Thanks!谢谢!

The reason why you are getting the warnings is the fact that MapStruct doesn't really care about the private / protected fields you have.您收到警告的原因是 MapStruct 并不真正关心您拥有的私有/受保护字段。

When performing a mapping MapStruct looks at the setters and the constructor parameters to decide which properties need to be mapped.在执行映射时,MapStruct 查看设置器和构造器参数来决定需要映射哪些属性。

Looking at your examples you have annotated the constructor that takes Base as a default constructor.查看您的示例,您已经注释了将Base作为默认构造函数的构造函数。 This means that from the point of view of MapStruct your objects have properties that are taking Base and thus there is the warning for the unmapped property base .这意味着从 MapStruct 的角度来看,您的对象具有占用Base的属性,因此存在未映射属性base的警告。

There are 2 ways that you can do to fix this:有两种方法可以解决此问题:

Instruct MapStruct how to map to the base property指示 MapStruct 如何将 map 转换为基础属性

@Mapper public interface SimpleMapper { SimpleMapper INSTANCE = Mappers.getMapper(SimpleMapper.class); @Mapper 公共接口 SimpleMapper { SimpleMapper INSTANCE = Mappers.getMapper(SimpleMapper.class);

@Mapping(target = "base", source = "target")
Source toSource(Target target);

@Mapping(target = "base", source = "source")
Target toTarget(Source source);

} }

using the @Mapping you will tell MapStruct that you want to map the source parameters to the base property.使用@Mapping你会告诉 MapStruct 你想要 map 源参数到base属性。

Annotated the constructor with the string a @Default使用字符串 a @Default注释构造函数

Instead of annotating the constructor that takes Base as input you can annotate the constructor that takes String as a default constructor.您可以注释将String作为默认构造函数的构造函数,而不是注释将Base作为输入的构造函数。

This way MapStruct will look for how to map a property with the name name and thus will use the public Base#getName method to perform the mapping.这样 MapStruct 将寻找如何 map 具有名称name的属性,因此将使用公共Base#getName方法来执行映射。


Note: I saw that you have @Qualifier on the @Default annotation, this is not needed.注意:我看到您在@Default注释上有@Qualifier ,这不是必需的。 The only requirement for MapStruct for the default annotation is that it needs to be named like that.默认注解对 MapStruct 的唯一要求是它需要这样命名。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM