【问题标题】:Map a dto to an entity retrieved from database if Dto has Id using MapStruct如果 Dto 使用 MapStruct 具有 Id,则将 dto 映射到从数据库中检索的实体
【发布时间】:2023-03-03 01:02:01
【问题描述】:

我正在使用 MapStruct 来制作 dto <-> entity 映射。相同的映射器用于从 dto创建 更新实体。对 dto 的 id 进行了验证,以了解是否必须创建新实体 (id == null) 或者应该从数据库中检索它 (id != null)。

我实际上使用MapperDecorator 作为解决方法。示例:

映射器

@Mapper
@DecoratedWith(UserAccountDecorator.class)
public interface UserAccountMapper {

    UserAccountDto map(User user);

    User map(UserAccountDto dto);

    User map(UserAccountDto dto, @MappingTarget User user);
}

装饰器

public abstract class UserAccountDecorator implements UserAccountMapper {

    @Autowired
    @Qualifier("delegate")
    private UserAccountMapper delegate;

    @Autowired
    private UserRepository userRepository;

    @Override
    public User map(UserAccountDto dto) {
        if (dto == null) {
            return null;
        }

        User user = new User();
        if (dto.getId() != null) {
            user = userRepository.findOne(dto.getId());
        }

        return delegate.map(dto, user);
    }

}

但是由于必须为每个映射器创建一个装饰器,这个解决方案变得很繁重。

有没有什么好的解决方案可以做到这一点?


我正在使用:

  1. MapStruct:1.1.0

【问题讨论】:

    标签: java mapping data-transfer-objects mapstruct


    【解决方案1】:

    我按照Gunnarcomment 中的建议解决了我的问题。

    我搬到MapStruct 1.2.0.Beta1 并创建了一个如下所示的 UserMapperResolver

    @Component
    public class UserMapperResolver {
    
        @Autowired
        private UserRepository userRepository;
    
        @ObjectFactory
        public User resolve(BaseUserDto dto, @TargetType Class<User> type) {
            return dto != null && dto.getId() != null ? userRepository.findOne(dto.getId()) : new User();
        }
    
    }
    

    然后我在我的 UserMapper 中使用它:

    @Mapper(uses = { UserMapperResolver.class })
    public interface BaseUserMapper {
    
        BaseUserDto map(User user);
    
        User map(BaseUserDto baseUser);
    
    }
    

    现在生成的代码是:

    @Override
        public User map(BaseUserDto baseUser) {
            if ( baseUser == null ) {
                return null;
            }
    
            User user = userMapperResolver.resolve( baseUser, User.class );
    
            user.setId( baseUser.getId() );
            user.setSocialMediaProvider( baseUser.getSocialMediaProvider() );
    ...
    }
    

    效果很好!

    【讨论】:

    • 您好 Radouane ROUFID,感谢您分享您的最终解决方案。我有一个小问题:让我们承认我想影响我的解析器,一个从我的存储库中检索到实体对象字段的对象。可以在 MapStruct 的对象工厂中执行此操作吗?没有找到任何类似的用例示例。 EG:我有一个用户在 OneToOne 关系中有一个电话实体。如果我得到一个包含 phoneId 的 UserDto 并且我想将它映射到具有实际电话实体的用户。谢谢!
    • 您可以在 UserResolver 中注入 PhoneRepository 并从数据库中获取电话实体。 @Autowired private UserRepository userRepository; @Autowired private PhoneREpository phoneRe; @ObjectFactory public User resolve(BaseUserDto dto, @TargetType Class&lt;User&gt; type) { User user = userRepository.findOne(dto.getId()); user.setPhone(phoneRep.getFIndOne(dto.getPhoneId())) return user; }
    • 是的,我会这样做,但在创建用户的情况下?我返回一个只有电话信息但没有其他信息的新用户项目?是在使用实际 DTO 完成对象之后调用 mapper 还是我们必须在解析器中执行所有映射逻辑?
    • ObjectFactory 创建/获取将在其中完成映射的对象。您可以在我的解决方案中查看生成的代码。
    【解决方案2】:

    单独的 MapStruct 无法做到这一点。但是,使用一些泛型和一个主要的抽象类可以让您的生活更轻松。

    您需要一个通用接口。一定不能用@Mapper注解,因为如果是MapStruct会尝试生成一个实现,它会失败。它无法生成通用映射器。

    public interface GenericMapper<E, DTO> {
    
        DTO map(E entity);
    
        E map(DTO dto);
    
        E map(DTO dto, @MappingTarget E entity);
    }
    

    那么你需要一个 abstract 类,你将拥有你的逻辑。

    public abstract class AbstractGenericMapper<E, DTO> implements GenericMapper<E, DTO> {
    
        @Autowired
        private Repository<E> repository;
    
        @Override
        public final E map (DTO dto) {
            if (dto == null) {
                return null;
            }
    
            // You can also use a Java 8 Supplier and pass it down the constructor
            E entity = newInstance();
            if (dto.getId() != null) {
                user = repository.findOne(dto.getId());
            }
    
            return map(dto, entity);
        }
    
        protected abstract E newInstance();
    }
    

    然后您的每个映射器只需要扩展这个abstract 类。

    @Mapper
    public abstract class UserAccountMapper extends AbstractGenericMapper<User, UserDto> {
    
        protected User newInstance() {
            return new User();
        }
    }
    

    MapStruct 然后将为您的映射器生成一个实现,您将来只需扩展AbstractGenericMapper。当然,您将需要调整通用参数,以便您至少可以通过某个接口从某个接口获取 id。如果您有不同类型的 id,那么您还必须将该通用参数添加到 AbstractGenericMapper

    【讨论】:

    • 谢谢Filip,我会试试这个解决方案!
    • 另一种方法可能是对象工厂方法接收MapStruct 1.2.0.Beta1(昨天发布)中启用的源对象。你可以有一个通用工厂方法,它要么实例化一个新实体,要么从数据库中加载它,具体取决于 DTO 状态(id 是否存在)。
    • 非常好,这个解决方案更好。谢谢 Gunnar,你做得很好!
    • 我也在考虑工厂方法返回对象的新可能性。但是,如果使用它,如果不是 null,MapStruct 将立即返回该对象
    猜你喜欢
    • 2017-09-17
    • 1970-01-01
    • 2014-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-03
    • 1970-01-01
    相关资源
    最近更新 更多