【问题标题】:MapStruct QualifiedByName with multiple parameters具有多个参数的 MapStruct QualifiedByName
【发布时间】:2018-05-21 12:39:23
【问题描述】:

我遇到过这样一种情况,我的映射方法有 3 个参数,而这三个参数都用于派生目标类型的属性之一。

我在接口中创建了一个默认映射方法,保留了派生属性的逻辑,现在为了调用这个方法,我可以在@Mapping 注释中使用expression = "java( /*method call here*/ )"

有没有办法用任何 mapstruct 注释来做到这一点,比如@qualifiedByName,我尝试评论具有表达式属性的注释并使用了qualifiedByName,但它不起作用:

@Mapper
public interface OneMapper {

    @Mapping(target="id", source="one.id")
    //@Mapping(target="qualified",expression = "java( checkQualified (one, projId, code) )")
    @Mapping(target="qualified",qualifiedByName="checkQualifiedNamed")
    OneDto createOne (One one, Integer projId, Integer val, String code);

    @Named("checkQualifiedNamed")
    default Boolean checkQualified (One one, Integer projId, Integer val, String code) {
        if(one.getProjectId() == projId && one.getVal() == val && one.getCode().equalsIgnoreCase(code)) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;                   
    }
}

【问题讨论】:

    标签: java intellij-idea java-8 mapstruct


    【解决方案1】:

    目前 MapStruct 不支持具有多个源属性的映射方法。

    但是,在您的情况下,您可以使用 1.2.0 中的 @Context。据我了解,projIdcode 只是作为映射的助手,它们不用于映射目标属性。

    所以你可以做类似的事情(理论上应该可行):

    @Mapper
    public interface OneMapper {
    
        @Mapping(target="id", source="one.id")
        @Mapping(target="qualified", qualifiedByName="checkQualifiedNamed")
        OneDto createOne (One one, @Context Integer projId, @Context String code);
    
        @Named("checkQualifiedNamed")
        default Boolean checkQualified (One one, @Context Integer projId, @Context String code) {
            if(one.getProjectId() == projId && one.getCode().equalsIgnoreCase(code)) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;                   
        }
    }
    

    另一种选择是将所有这些属性提取到一个单独的类中并传递(这将允许同一类型的多个参数)。

    这个类看起来像:

    public class Filter {
    
        private final Integer projId;
        private final Integer val;
        private final String code;
    
        public Filter (Integer projId, Integer val, String code) {
            this.projId = projId;
            this.val = val;
            this.code = code;
        }
    
        //getters
    }
    

    您的映射器将如下所示:

    @Mapper
    public interface OneMapper {
    
        @Mapping(target="id", source="one.id")
        @Mapping(target="qualified", qualifiedByName="checkQualifiedNamed")
        OneDto createOne (One one, @Context Filter filter);
    
        @Named("checkQualifiedNamed")
        default Boolean checkQualified (One one, @Context Filter filter) {
            if(one.getProjectId() == filter.getProjId() && one.getVal() == filter.getVal() && one.getCode().equalsIgnoreCase(filter.getCode())) {
                return Boolean.TRUE;
            }
            return Boolean.FALSE;                   
        }
    }
    

    然后您可以像这样调用映射器:mapper.createOne(one, new Filter(projId, val, code));

    【讨论】:

    • 感谢@Filip 的输入,这似乎非常接近我的要求,我这里还有一个问题,在我的实际项目中,有 4 个参数,一个实体对象和 3 个支持属性,三个支持属性中的 2 个是整数,并且不能有多个具有相同类型的上下文,我尝试将实体作为上下文并将其中一个整数保留在源中,但这样它会忽略实体中的所有其他属性。如果您可以为此提出建议,我已经更新了我的问题中的代码 sn-p?
    • 我已经更新了答案。基本上,您可以用代表@Context 的包装类替换您拥有的所有属性。那一个功能更强大,可以让您执行一系列操作。查看passing context / state objects的文档
    • 在我的应用程序中为过滤器定义一个新类可能很困难,但是我会检查可行性,并决定两者之一,使用表达式或定义一个文件管理器。感谢您的努力和时间。
    • 我不知道你的代码,所以我不能说。无论如何,不​​允许相同的类型是因为我们无法真正做好映射哪个和哪个的正确工作。顺便说一句,如果它回答了您的问题,您应该接受答案?
    • 非常感谢,它帮助我部分解决了我的问题。但是,它对我不起作用。我必须在@Mapping(target="qualified", qualifiedByName="checkQualifiedNamed") 中指定来源。然后如何将 2 个参数传递给 checkQualified? (在我的情况下,第二个参数不是@Context)
    【解决方案2】:

    从 1.2 版开始支持: http://mapstruct.org/documentation/stable/reference/html/#mappings-with-several-source-parameters

    例如这样:

    @Mapping(source = "person.description", target = "description")
    @Mapping(source = "address.houseNo", target = "houseNumber")
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);
    

    更新

    由于 Mapstruct 允许将多个源参数映射到单个目标,我建议从映射器中提取 checkQualified 方法,而不是预先计算结果并使用 checkQualified 方法的结果调用映射器。 Mapstruct 是一个 mapping 库,不擅长执行任意逻辑。这并非不可能,但就个人而言,我看不出它在您的特定情况下增加的价值。

    提取逻辑后,您的映射器可能如下所示:

    @Mapper
    public interface OneMapper {
        OneDto toOneDto(One one, Boolean qualified);
    }
    

    映射器可以这样使用:

    One one = new One(1, 10, 100, "one");
    boolean qualified = checkQualified(one, 10, 100, "one");
    boolean notQualified = checkQualified(one, 10, 100, "two");
    OneDto oneDto = mapper.toOneDto(one, isQualified);
    

    有关完整示例,请参阅:https://github.com/phazebroek/so-mapstruct/blob/master/src/main/java/nl/phazebroek/so/MapStructDemo.java

    【讨论】:

    • 你还没有告诉任何关于qualifiedByName ((
    • 因为没有必要使用它。如果您有多个具有相同方法签名但行为不同的映射方法,则qualifiedby 很方便。无论如何,你引起了我的好奇心,我已经用一个更详细的例子更新了我的答案,以更接近于 OP 代码。
    【解决方案3】:

    如果您需要基于来自同一源对象的多个源字段计算单个目标字段,您可以将完整源对象传递给自定义映射器函数,而不是单个字段:

    示例实体:

    @Entity
    @Data
    public class User {
        private String firstName;
        private String lastName;
    }
    
    

    DTO 示例:

    
    public class UserDto {
        private String fullName;
    }
    

    ...和映射器...而不是传递单个源(名字):

    @Mapper
    public abstract class UserMapper {
    
        @Mapping(source = "firstName", target = "fullName", qualifiedByName = "nameTofullName")
        public abstract UserDto userEntityToUserDto(UserEntity userEntity);
    
    
        @Named("nameToFullName")
        public String nameToFullName(String firstName) {
            return String.format("%s HOW DO I GET THE LAST NAME HERE?", firstName);
        }
    

    ...传递完整的实体对象(userEntity)作为源:

    @Mapper
    public abstract class UserMapper {
    
        @Mapping(source = "userEntity", target = "fullName", qualifiedByName = "nameToFullName")
        public abstract UserDto userEntityToUserDto(UserEntity userEntity);
    
    
        @Named("nameToFullName")
        public String nameToOwner(UserEntity userEntity) {
            return String.format("%s %s", userEntity.getFirstName(), userEntity.getLastName());
        }
    

    【讨论】:

    • 听起来不错,但不起作用。 ~D.J. T.
    【解决方案4】:

    您可以创建一个默认方法,该方法在内部调用带有附加上下文参数的mapstruct方法。这样,您可以获得'qualifiedByName'部分中的所有参数

    @Mapper
    public interface OneMapper {
    
        default OneDto createOne(One one, Integer projId, Integer val, String code) {
            return createOneWithContext(one,porjId,val,code
                                        one,porjId,val,code //as context params
            );
        }
    
        @Mapping(target="id", source="one.id")
        @Mapping(target="qualified",source="one",qualifiedByName="checkQualifiedNamed")
        OneDto createOneWithContext (One one, Integer projId, Integer val, String code
                         @Context One oneAsContext, 
                         @Context Integer projIdAsContext, 
                         @Context Integer valAsContext, 
                         @Context String codeAsContext
        
    );
    
        @Named("checkQualifiedNamed")
        default Boolean checkQualified (One one, @Context Integer projId, @Context Integer val, @Context String code) {
            if(one.getProjectId() == projId && one.getVal() == val && one.getCode().equalsIgnoreCase(code)) {
                return Boolean.TRUE;
            }
        return Boolean.FALSE;                   
        }
    }
    
    
    
    ```
    

    【讨论】:

      猜你喜欢
      • 2021-10-25
      • 2020-12-21
      • 2022-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-21
      • 2011-04-15
      • 2011-08-01
      相关资源
      最近更新 更多