Jin Kwon
Jin Kwon

Reputation: 21988

MapStruct generates wrong mapping code without any inverse config?

I have a @MapperConfig looks like this.

@MapperConfig(componentModel = "spring")
public interface SomeEntityTypeMapperConfig {

    @Mapping(target = PROPERTY_NAME_ENTITY)
    @Mapping(source = SomeEntity.ATTRIBUTE_NAME_ID, target = SomeEntityType.PROPERTY_NAME_ID)
    @Mapping(source = SomeEntity.PROPERTY_NAME_CREATED_AT, target = SomeEntityType.PROPERTY_NAME_CREATED_AT)
    @Mapping(source = SomeEntity.PROPERTY_NAME_UPDATED_AT, target = SomeEntityType.PROPERTY_NAME_UPDATED_AT)
    @Mapping(source = SomeEntity.PROPERTY_NAME_CREATED_BY, target = SomeEntityType.PROPERTY_NAME_CREATED_BY)
    @Mapping(source = SomeEntity.PROPERTY_NAME_UPDATED_BY, target = SomeEntityType.PROPERTY_NAME_UPDATED_BY)
    SomeEntityType<?, ?> fromEntity(SomeEntity entity);

    // No @Mapping
    void toEntity(SomeEntityType<?, ?> type, @MappingTarget SomeEntity entity);
}

Here comes my base mapper interface.

public interface SomeEntityTypeMapper<T extends SomeEntityType<?, U>, U extends SomeEntity> {

    T fromEntity(U entity);

    void toEntity(T type, @MappingTarget U entity);
}

And here comes my real mapper.

@Mapper(config = SomeEntityTypeMapperConfig.class)
public interface UserTypeMapper extends SomeEntityTypeMapper<UserType, User> {

    @Mapping(source = User.ATTRIBUTE_NAME_NAME, target = UserType.PROPERTY_NAME_NAME)
    @Override
    UserType fromEntity(User entity);

    @Mapping(source = UserType.PROPERTY_NAME_NAME, target = User.ATTRIBUTE_NAME_NAME)
    @Override
    void toEntity(UserType type, @MappingTarget User entity);
}

And MapStruct generates following impl class with unwanted mappings in it.

public class UserTypeMapperImpl implements UserTypeMapper {

    @Override
    public UserType fromEntity(User entity) {
        if ( entity == null ) {
            return null;
        }
        UserType userType = new UserType();
        userType.setName( entity.getName() );           // explicitly configured
        userType.setId( entity.getId() );               // inherited from the config
        userType.setCreatedAt( entity.getCreatedAt() ); // inherited from the config
        userType.setUpdatedAt( entity.getUpdatedAt() ); // inherited from the config
        userType.setCreatedBy( entity.getCreatedBy() ); // inherited from the config
        userType.setUpdatedBy( entity.getUpdatedBy() ); // inherited from the config
        return userType;
    }

    @Override
    public void toEntity(UserType type, User entity) {
        if ( type == null ) {
            return;
        }
        entity.setName( type.getName() );           // explicitly configured
        entity.setCreatedAt( type.getCreatedAt() ); // UNWANTED!!!
        entity.setUpdatedAt( type.getUpdatedAt() ); // UNWANTED!!!
        entity.setUpdatedBy( type.getUpdatedBy() ); // UNWANTED!!!
        entity.setId( type.getId() );               // UNWANTED!!!
        entity.setCreatedBy( type.getCreatedBy() ); // UNWANTED!!!
    }
}

What did I do wrong and How can I fix it?

Upvotes: 0

Views: 541

Answers (2)

Filip
Filip

Reputation: 21403

What you are referring as unwanted reverse mapping without any annotations is actually the normal way that MapStruct generates mappings. If the source and target beans have the same property (which they do in your case) MapStruct would create a mapping for it.

In case you don't want to map some properties you can either ignore those one by one or use @BeanMapping( ignoreByDefault = true). With the second option MapStruct would only create mappings for the defined @Mapping.

Upvotes: 1

Jin Kwon
Jin Kwon

Reputation: 21988

I'm sharing what I found.

I needed to annotate the method with @BeanMapping(ignoreByDefault = true).

Interestingly the annotation must be located with leaf mapper interfaces.

@BeanMapping(ignoreByDefault = true) // WORKS!!!
@Mapping(source = UserType.PROPERTY_NAME_NAME, target = User.ATTRIBUTE_NAME_NAME)
@Override
void toEntity(UserType type, @MappingTarget User entity);

Didn't work with configuration nor parent interface.

public interface SomeEntityTypeMapperConfig {

    @BeanMapping(ignoreByDefault = true) // Doesn't work!
    void toEntity(SomeEntityType<?, ?> type, @MappingTarget SomeEntity entity);
}
public interface SomeEntityTypeMapper<T extends SomeEntityType<?, U>, U extends SomeEntity> {

    @BeanMapping(ignoreByDefault = true) // Doesn't work!
    void toEntity(T type, @MappingTarget U entity);
}

Upvotes: 0

Related Questions