如果 Dto 使用 MapStruct 有 Id,则将 dto 映射到从数据库检索的实体

问题描述 投票:0回答:3

我正在使用 MapStruct 进行

dto <-> entity
映射。相同的映射器用于从 dtos create andupdate 实体。对 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. 地图结构:1.1.0
java mapping data-transfer-objects mapstruct
3个回答
24
投票

我按照评论Gunnar的建议解决了我的问题。

我移至 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() );
...
}

效果很好!


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


0
投票

基于 #Radouane ROUFID 答案和我在这里读到的其他一些文章,这是一种更通用的方法,可以避免为每个实体都有一个解析器。

首先需要明确什么是DTO,所有的DTO都需要一个getId()方法:

public abstract class AbstractIdDto {

    public abstract Long getId();

}

所有应自动检索实体的 DTO 都应从此 DTO 扩展:

@Schema(description = "Main incidents object.")
public class Incident extends AbstractIdDto {

    @Schema(description = "Unique id of the incident.")
    private Long id;

    @Schema(description = "User which created this incident.")
    private String userId;

    ...

}

创建一个读取数据库实体或初始化新实体的 MapStruct 对象工厂:

@ApplicationScoped
public class EntityResolverMapper {

    @PersistenceContext
    private EntityManager entityManager;

    /**
     * Automatically fetches related DB entity referenced by id
     * e.g. (Entity) User user <--> userId (DTO)
     * @param <T>
     * @param id
     * @param entityClass
     * @return the DB Entity or null if not found
     */
    public <T extends DatabaseEntity> T resolve(Long id, @TargetType Class<T> entityClass) {
        return id != null ? entityManager.find(entityClass, id) : null;
    }

    /**
     * Automatically fetches the DB entity referenced by the id of the DTO
     * @param <T>
     * @param sourceDto
     * @param type
     * @return the DB Entity if found or a new instance of the object
     * @throws AppWebException
     */
    @ObjectFactory
    public <T extends DatabaseEntity> T resolve(AbstractIdDto sourceDto, @TargetType Class<T> type) throws AppWebException {
        if (sourceDto.getId() != null) { 
            T entity = entityManager.find(type, sourceDto.getId());
            if (entity != null) {
                return entity;
            } else {
                throw new ResourceNotFoundException(String.format("Not found %s (%d)", type, sourceDto.getId()));
            }
        }
        try {
            return type.getDeclaredConstructor().newInstance();
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
                | NoSuchMethodException | SecurityException e) {
            throw new MappingException(String.format("Failed to instantiate object %s", type), e);
        }
    }
}

在Mapper中使用新的EntityResolverMapper:

@Mapper(componentModel = MappingConstants.ComponentModel.JAKARTA, injectionStrategy = InjectionStrategy.CONSTRUCTOR, uses = {EntityResolverMapper.class})
public interface IncidentMapper {

    @Mapping(target = "userId", source = "user.id")
    @Mapping(target = "userEmail", source = "user.email")
    @Mapping(target = "userName", source = "user.username")
    Incident entityToDto(IncidentEntity entity);

    @Mapping(target = "user", source = "userId")
    @Mapping(target = "modifiedAt", ignore = true)
    IncidentEntity dtoToEntity(Incident incident) throws AppWebException;
}

为了完整起见 - 这里是实体:

@Entity
@Table(name = "INCIDENTS")
public class IncidentEntity extends DatabaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_INCIDENT_ID")
    @SequenceGenerator(allocationSize = 1, name = "SEQ_INCIDENT_ID", sequenceName = "SEQ_INCIDENT_ID")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name="user_id", nullable = false)
    private UserEntity user;

    private String title;

    private String description;

    ...
}

生成以下 MapStruct 代码:


@Override
public IncidentEntity dtoToEntity(Incident incident) throws AppWebException{
    if ( incident == null ) {
        return null;
    }

    IncidentEntity incidentEntity = entityResolverMapper.resolve( incident, IncidentEntity.class );

    if ( incident != null ) {
        if ( incident.getUserId() != null ) {
            incidentEntity.setUser( entityResolverMapper.resolve( Long.parseLong( incident.getUserId() ), UserEntity.class ) );
        }
        incidentEntity.setId( incident.getId() );
        incidentEntity.setTitle( incident.getTitle() );
        ...
        }
    }
 }
© www.soinside.com 2019 - 2024. All rights reserved.