Java 有没有办法防止MapStruct在更新记录期间覆盖值?

Java 有没有办法防止MapStruct在更新记录期间覆盖值?,java,spring,spring-boot,mapping,mapstruct,Java,Spring,Spring Boot,Mapping,Mapstruct,我看到2018年有一个类似的问题: 但没有解决这个问题的例子 因此,我不知道如何解决它 我正在使用Lombok和MapStruct UserEntity表示数据库中的表 @Getter @Setter @Entity @Table(name = "users") public class UserEntity implements Serializable { private static final long serialVersionUID = -3549451

我看到2018年有一个类似的问题: 但没有解决这个问题的例子

因此,我不知道如何解决它

我正在使用
Lombok
MapStruct

UserEntity
表示数据库中的表

@Getter
@Setter
@Entity
@Table(name = "users")
public class UserEntity implements Serializable {

    private static final long serialVersionUID = -3549451006888843499L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)     // this specifies that the id will be auto-incremented by the database
    private Long id;
    
    @Column( nullable = false)
    private String userId;
    
    @Column( nullable = false, length = 50)
    private String firstName;
    
    @Column( nullable = false, length = 50)
    private String lastName;
    
    @Column( nullable = false, length = 120)
    private String email;
    
    @Column( nullable = false)
    private String encryptedPassword;   // I am encrypting the password during first insertion of a new record

}// end of class
UserDTO
用作
UserEntity
UserRequestModel
之间的中介(我将在后面提到)

UserRequestModel
在请求到达UserController时使用

@Getter
@Setter
@ToString
public class UserRequestModel {
    
//  ------------------------------------------------------
//  Attributes
//  ------------------------------------------------------
    private String firstName;
    private String lastName;
    private String email;
    
}// end of class
我创建了一个
UserMapper
接口,供
MapStruct
使用

@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface UserMapper {

    @Mapping(target = "password", ignore = true)
    UserDTO mapEntityToDto(UserEntity userEntity);

    @Mapping(target = "encryptedPassword")
    UserEntity mapDtoToEntity(UserDTO userDto);
    
    UserResponseModel mapDtoToResponseModel(UserDTO userDto);
    
    @Mapping(target = "encryptedPassword", ignore = true)
    @Mapping(target = "id", ignore = true)
    @Mapping(target = "userId", ignore = true)
    UserDTO mapUserRequestModelToDto(UserRequestModel userRequestModel);
    
}// end of interface
最后,我使用
UserResponseModel
将记录返回给客户端应用程序

@Getter
@Setter
@ToString
public class UserResponseModel {
    
//  ------------------------------------------------------
//  Attributes
//  ------------------------------------------------------
    
    // This is NOT the actual usedId in the database!!!
    // We should not provide the actual value. For security reasons.
    // Think of it as a public user id.
    private String userId;
    private String firstName;
    private String lastName;
    private String email;

}// end of class
@Override
public UserDTO updateUser(String userId, UserDTO user) {
        
    UserDTO returnValue = new UserDTO();
        
    UserEntity userEntity = userRepository.findByUserId(userId);
        
    if(userEntity == null) {
        throw new UserServiceException("No record found with the specific id!");        // our own exception
    }
        
    // get the changes from the DTO and map them to the Entity
    // this did not work. The values of the Entity were set to null if they were not assigned in the DTO
    userEntity = userMapper.mapDtoToEntity(user);
        
    // If I set the values of the Entity manually, then it works
//      userEntity.setFirstName(user.getFirstName());
//      userEntity.setLastName(user.getLastName());
//      userEntity.setEmail(user.getEmail());
        
    // save the Entity
    userRepository.save(userEntity);
        
    returnValue = userMapper.mapEntityToDto(userEntity);
        
    return returnValue;
        
}// end of updateUser
上面的对象用于映射。现在,我将向您展示
UserController
UserService
中的代码。此问题发生在
UserService
类中

@PutMapping(path = "/{id}")
public UserResponseModel updateUser(@PathVariable("id") String id , @RequestBody UserRequestModel userRequestModel) {
        
    UserResponseModel returnValue = new UserResponseModel();
        
    UserDTO userDTO = userMapper.mapUserRequestModelToDto(userRequestModel);
        
    // call the method to update the user and return the updated object as a UserDTO
    UserDTO updatedUser = userService.updateUser(id, userDTO);
        
    returnValue = userMapper.mapDtoToResponseModel(updatedUser);
        
    return returnValue;
        
}// end of updateUser
@Mapping(target = "firstName", source = "firstName")
@Mapping(target = "lastName", source = "lastName")
@Mapping(target = "email", source = "email")
void updateFields(@MappingTarget UserEntity entity, UserDTO dto);
@Override
public UserDTO updateUser(String userId, UserDTO user) {
        
    UserDTO returnValue = new UserDTO();

    UserEntity userEntity = userRepository.findByUserId(userId);

    if (userEntity == null)
            throw new UserServiceException(ErrorMessages.NO_RECORD_FOUND.getErrorMessage());
        
    // update only specific fields of userEntity
    userMapper.updateFields( userEntity, user);

    userRepository.save(userEntity);
        
    // map again UserEntity to UserDTO in order to send it back
    returnValue = userMapper.mapEntityToDto(userEntity);

    return returnValue;
        
}// end of updateUser
UserService
用于实现应用程序的业务逻辑

@Getter
@Setter
@ToString
public class UserResponseModel {
    
//  ------------------------------------------------------
//  Attributes
//  ------------------------------------------------------
    
    // This is NOT the actual usedId in the database!!!
    // We should not provide the actual value. For security reasons.
    // Think of it as a public user id.
    private String userId;
    private String firstName;
    private String lastName;
    private String email;

}// end of class
@Override
public UserDTO updateUser(String userId, UserDTO user) {
        
    UserDTO returnValue = new UserDTO();
        
    UserEntity userEntity = userRepository.findByUserId(userId);
        
    if(userEntity == null) {
        throw new UserServiceException("No record found with the specific id!");        // our own exception
    }
        
    // get the changes from the DTO and map them to the Entity
    // this did not work. The values of the Entity were set to null if they were not assigned in the DTO
    userEntity = userMapper.mapDtoToEntity(user);
        
    // If I set the values of the Entity manually, then it works
//      userEntity.setFirstName(user.getFirstName());
//      userEntity.setLastName(user.getLastName());
//      userEntity.setEmail(user.getEmail());
        
    // save the Entity
    userRepository.save(userEntity);
        
    returnValue = userMapper.mapEntityToDto(userEntity);
        
    return returnValue;
        
}// end of updateUser
如果我尝试运行此代码,则会出现以下错误

我使用调试器来理解这个问题,我注意到
MapStruct
正在覆盖所有属性,而不仅仅是请求中发送的属性

MapStruct
自动生成的代码如下:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-11-01T11:56:36+0200",
    comments = "version: 1.4.1.Final, compiler: Eclipse JDT (IDE) 3.21.0.v20200304-1404, environment: Java 11.0.9 (Ubuntu)"
)
@Component
public class UserMapperImpl implements UserMapper {

    @Override
    public UserDTO mapEntityToDto(UserEntity userEntity) {
        if ( userEntity == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setEmail( userEntity.getEmail() );
        userDTO.setEncryptedPassword( userEntity.getEncryptedPassword() );
        userDTO.setFirstName( userEntity.getFirstName() );
        userDTO.setId( userEntity.getId() );
        userDTO.setLastName( userEntity.getLastName() );
        userDTO.setUserId( userEntity.getUserId() );

        return userDTO;
    }

    @Override
    public UserEntity mapDtoToEntity(UserDTO userDto) {
        if ( userDto == null ) {
            return null;
        }

        UserEntity userEntity = new UserEntity();

        userEntity.setEncryptedPassword( userDto.getEncryptedPassword() );
        userEntity.setEmail( userDto.getEmail() );
        userEntity.setFirstName( userDto.getFirstName() );
        userEntity.setId( userDto.getId() );
        userEntity.setLastName( userDto.getLastName() );
        userEntity.setUserId( userDto.getUserId() );

        return userEntity;
    }

    @Override
    public UserResponseModel mapDtoToResponseModel(UserDTO userDto) {
        if ( userDto == null ) {
            return null;
        }

        UserResponseModel userResponseModel = new UserResponseModel();

        userResponseModel.setEmail( userDto.getEmail() );
        userResponseModel.setFirstName( userDto.getFirstName() );
        userResponseModel.setLastName( userDto.getLastName() );
        userResponseModel.setUserId( userDto.getUserId() );

        return userResponseModel;
    }

    @Override
    public UserDTO mapUserRequestModelToDto(UserRequestModel userRequestModel) {
        if ( userRequestModel == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setEmail( userRequestModel.getEmail() );
        userDTO.setFirstName( userRequestModel.getFirstName() );
        userDTO.setLastName( userRequestModel.getLastName() );
        userDTO.setPassword( userRequestModel.getPassword() );

        return userDTO;
    }
}
@Mapping(target = "targetFieldName", source = "sourceFieldName")
void updateFields(@MappingTarget UserEntity entity, UserDTO dto);
很可能,我做的事情不对

你认为问题的出现是因为我没有构造函数吗?通常,如果我们不实现无参数构造函数,Java会自动创建它

我正在添加下图,以便演示我想做的事情。也许这种流动能有所帮助

很可能方法
MapdToEntity
应该接受两个属性,以便将
UserDTO
映射到
UserEntity
。例如:

userMapper.mapDtoToEntity(userDto, userEntity);

谢谢你!!!我在我的代码中添加了你的修改,一切正常

更新版本

我不得不将以下方法添加到
UserMapper
类中

@PutMapping(path = "/{id}")
public UserResponseModel updateUser(@PathVariable("id") String id , @RequestBody UserRequestModel userRequestModel) {
        
    UserResponseModel returnValue = new UserResponseModel();
        
    UserDTO userDTO = userMapper.mapUserRequestModelToDto(userRequestModel);
        
    // call the method to update the user and return the updated object as a UserDTO
    UserDTO updatedUser = userService.updateUser(id, userDTO);
        
    returnValue = userMapper.mapDtoToResponseModel(updatedUser);
        
    return returnValue;
        
}// end of updateUser
@Mapping(target = "firstName", source = "firstName")
@Mapping(target = "lastName", source = "lastName")
@Mapping(target = "email", source = "email")
void updateFields(@MappingTarget UserEntity entity, UserDTO dto);
@Override
public UserDTO updateUser(String userId, UserDTO user) {
        
    UserDTO returnValue = new UserDTO();

    UserEntity userEntity = userRepository.findByUserId(userId);

    if (userEntity == null)
            throw new UserServiceException(ErrorMessages.NO_RECORD_FOUND.getErrorMessage());
        
    // update only specific fields of userEntity
    userMapper.updateFields( userEntity, user);

    userRepository.save(userEntity);
        
    // map again UserEntity to UserDTO in order to send it back
    returnValue = userMapper.mapEntityToDto(userEntity);

    return returnValue;
        
}// end of updateUser
然后,我不得不修改
UserService
类中的
updateUser
方法

@PutMapping(path = "/{id}")
public UserResponseModel updateUser(@PathVariable("id") String id , @RequestBody UserRequestModel userRequestModel) {
        
    UserResponseModel returnValue = new UserResponseModel();
        
    UserDTO userDTO = userMapper.mapUserRequestModelToDto(userRequestModel);
        
    // call the method to update the user and return the updated object as a UserDTO
    UserDTO updatedUser = userService.updateUser(id, userDTO);
        
    returnValue = userMapper.mapDtoToResponseModel(updatedUser);
        
    return returnValue;
        
}// end of updateUser
@Mapping(target = "firstName", source = "firstName")
@Mapping(target = "lastName", source = "lastName")
@Mapping(target = "email", source = "email")
void updateFields(@MappingTarget UserEntity entity, UserDTO dto);
@Override
public UserDTO updateUser(String userId, UserDTO user) {
        
    UserDTO returnValue = new UserDTO();

    UserEntity userEntity = userRepository.findByUserId(userId);

    if (userEntity == null)
            throw new UserServiceException(ErrorMessages.NO_RECORD_FOUND.getErrorMessage());
        
    // update only specific fields of userEntity
    userMapper.updateFields( userEntity, user);

    userRepository.save(userEntity);
        
    // map again UserEntity to UserDTO in order to send it back
    returnValue = userMapper.mapEntityToDto(userEntity);

    return returnValue;
        
}// end of updateUser

事实上,每次执行PUT时,您都在创建一个新的
UserEntity
实例。应该做的是更新所需字段。为此,您可以按如下方式使用
@MappingTarget

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-11-01T11:56:36+0200",
    comments = "version: 1.4.1.Final, compiler: Eclipse JDT (IDE) 3.21.0.v20200304-1404, environment: Java 11.0.9 (Ubuntu)"
)
@Component
public class UserMapperImpl implements UserMapper {

    @Override
    public UserDTO mapEntityToDto(UserEntity userEntity) {
        if ( userEntity == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setEmail( userEntity.getEmail() );
        userDTO.setEncryptedPassword( userEntity.getEncryptedPassword() );
        userDTO.setFirstName( userEntity.getFirstName() );
        userDTO.setId( userEntity.getId() );
        userDTO.setLastName( userEntity.getLastName() );
        userDTO.setUserId( userEntity.getUserId() );

        return userDTO;
    }

    @Override
    public UserEntity mapDtoToEntity(UserDTO userDto) {
        if ( userDto == null ) {
            return null;
        }

        UserEntity userEntity = new UserEntity();

        userEntity.setEncryptedPassword( userDto.getEncryptedPassword() );
        userEntity.setEmail( userDto.getEmail() );
        userEntity.setFirstName( userDto.getFirstName() );
        userEntity.setId( userDto.getId() );
        userEntity.setLastName( userDto.getLastName() );
        userEntity.setUserId( userDto.getUserId() );

        return userEntity;
    }

    @Override
    public UserResponseModel mapDtoToResponseModel(UserDTO userDto) {
        if ( userDto == null ) {
            return null;
        }

        UserResponseModel userResponseModel = new UserResponseModel();

        userResponseModel.setEmail( userDto.getEmail() );
        userResponseModel.setFirstName( userDto.getFirstName() );
        userResponseModel.setLastName( userDto.getLastName() );
        userResponseModel.setUserId( userDto.getUserId() );

        return userResponseModel;
    }

    @Override
    public UserDTO mapUserRequestModelToDto(UserRequestModel userRequestModel) {
        if ( userRequestModel == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setEmail( userRequestModel.getEmail() );
        userDTO.setFirstName( userRequestModel.getFirstName() );
        userDTO.setLastName( userRequestModel.getLastName() );
        userDTO.setPassword( userRequestModel.getPassword() );

        return userDTO;
    }
}
@Mapping(target = "targetFieldName", source = "sourceFieldName")
void updateFields(@MappingTarget UserEntity entity, UserDTO dto);

此注释确保不创建新对象,它将维护原始对象,只更新所需字段。

事实上,每次执行PUT时,您都在创建一个新的
UserEntity
实例。应该做的是更新所需字段。为此,您可以按如下方式使用
@MappingTarget

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-11-01T11:56:36+0200",
    comments = "version: 1.4.1.Final, compiler: Eclipse JDT (IDE) 3.21.0.v20200304-1404, environment: Java 11.0.9 (Ubuntu)"
)
@Component
public class UserMapperImpl implements UserMapper {

    @Override
    public UserDTO mapEntityToDto(UserEntity userEntity) {
        if ( userEntity == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setEmail( userEntity.getEmail() );
        userDTO.setEncryptedPassword( userEntity.getEncryptedPassword() );
        userDTO.setFirstName( userEntity.getFirstName() );
        userDTO.setId( userEntity.getId() );
        userDTO.setLastName( userEntity.getLastName() );
        userDTO.setUserId( userEntity.getUserId() );

        return userDTO;
    }

    @Override
    public UserEntity mapDtoToEntity(UserDTO userDto) {
        if ( userDto == null ) {
            return null;
        }

        UserEntity userEntity = new UserEntity();

        userEntity.setEncryptedPassword( userDto.getEncryptedPassword() );
        userEntity.setEmail( userDto.getEmail() );
        userEntity.setFirstName( userDto.getFirstName() );
        userEntity.setId( userDto.getId() );
        userEntity.setLastName( userDto.getLastName() );
        userEntity.setUserId( userDto.getUserId() );

        return userEntity;
    }

    @Override
    public UserResponseModel mapDtoToResponseModel(UserDTO userDto) {
        if ( userDto == null ) {
            return null;
        }

        UserResponseModel userResponseModel = new UserResponseModel();

        userResponseModel.setEmail( userDto.getEmail() );
        userResponseModel.setFirstName( userDto.getFirstName() );
        userResponseModel.setLastName( userDto.getLastName() );
        userResponseModel.setUserId( userDto.getUserId() );

        return userResponseModel;
    }

    @Override
    public UserDTO mapUserRequestModelToDto(UserRequestModel userRequestModel) {
        if ( userRequestModel == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setEmail( userRequestModel.getEmail() );
        userDTO.setFirstName( userRequestModel.getFirstName() );
        userDTO.setLastName( userRequestModel.getLastName() );
        userDTO.setPassword( userRequestModel.getPassword() );

        return userDTO;
    }
}
@Mapping(target = "targetFieldName", source = "sourceFieldName")
void updateFields(@MappingTarget UserEntity entity, UserDTO dto);

此注释确保不创建新对象,它将维护原始对象,只更新所需的字段。

我不确定是否理解UserEntity中字段“userId”和“id”的含义。它是否引用外键?如果是这种情况,您可以在持久化实体之前注入父(或子)对象。Hello@Sergio,我正在使用UserEntity中的id来表示表的主键。出于安全原因,我不想将此id发送到客户端应用程序。因此,我创建了一个用户ID,用作公钥。您使用哪个数据库?我不确定是否理解公钥的概念。它是否引用数据库中的另一个表?因为在您显示的错误消息中,两个表之间的链接似乎不满足要求。我只有一个名为UserEntity的表,它具有一个名为id的自动递增主键。因为我不想向客户端应用程序显示主键的实际值,所以我使用了另一个名为userId的属性。这是出于安全考虑我想要的东西。出现此问题的原因是MapStruct创建了一个新的UserEntity,并且UserEntity中的值encryptedPassword设置为NULL。encryptedPassword不能为空!也许您没有复制它,但我看不到从密码到encryptedPassword的位置,因为它不在来自控制器的传入UserRequestModel中。您是否为encryptedPassword生成随机值?我不确定您的UserEntity中“userId”和“id”字段的含义。它是否引用外键?如果是这种情况,您可以在持久化实体之前注入父(或子)对象。Hello@Sergio,我正在使用UserEntity中的id来表示表的主键。出于安全原因,我不想将此id发送到客户端应用程序。因此,我创建了一个用户ID,用作公钥。您使用哪个数据库?我不确定是否理解公钥的概念。它是否引用数据库中的另一个表?因为在您显示的错误消息中,两个表之间的链接似乎不满足要求。我只有一个名为UserEntity的表,它具有一个名为id的自动递增主键。因为我不想向客户端应用程序显示主键的实际值,所以我使用了另一个名为userId的属性。这是出于安全考虑我想要的东西。出现此问题的原因是MapStruct创建了一个新的UserEntity,并且UserEntity中的值encryptedPassword设置为NULL。encryptedPassword不能为空!也许您没有复制它,但我看不到从密码到encryptedPassword的位置,因为它不在来自控制器的传入UserRequestModel中。您是否为encryptedPassword生成随机值?谢谢@Sergio!修改后一切正常!!!谢谢你@Sergio!修改后一切正常!!!