有没有办法防止 MapStruct 在记录更新期间覆盖值?
Is there a way to prevent MapStruct from overwriting values during the update of a record?
我看到2018年有一个类似的问题:
但是没有解决这个问题的例子。
所以一直想不出怎么解决。
我正在使用 Lombok
和 MapStruct
UserEntity
表示数据库
中的table
@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
之间的(后面会提到)。
@Getter
@Setter
@ToString
public class UserDTO implements Serializable {
private static final long serialVersionUID = -2583091281719120521L;
// ------------------------------------------------------
// Attributes
// ------------------------------------------------------
private Long id; // actual id from the database table
private String userId; // public user id
private String firstName;
private String lastName;
private String email;
private String password;
private String encryptedPassword; // in the database we store the encrypted password
}// end of class
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
来return一条记录到客户端应用。
@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
上面的对象用于映射。现在,我将向您展示 UserController
和 UserService
中的代码。此问题发生在 UserService
class.
中
@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
UserService
用于实现应用的业务逻辑。
@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;
}
}
很可能是我做错了什么。
您认为问题的发生是因为我没有构造函数吗?通常 Java 自动创建无参数构造函数,如果我们不实现的话。
我添加了下面的图片,这样我就可以演示我想要做什么。也许这个流程可以提供帮助。
最有可能的方法 mapDtoToEntity
应该接受 2 个属性 以便将 UserDTO
映射到 UserEntity
。例如:
userMapper.mapDtoToEntity(userDto, userEntity);
public UserEntity mapDtoToEntity(UserDTO userDto, UserEntity userEntity) {
if( userDto == null ) {
return null;
}
// set ONLY the attributes of the UserEntity that were assigned
// to the UserDTO by the UserRequestModel. In the current case only 3 attributes
// return the updated UserEntity
}
谢谢Sergio Lema!!!我在我的代码中添加了您的修改,一切正常!
更新版本
我必须将以下方法添加到 UserMapper
class。
@Mapping(target = "firstName", source = "firstName")
@Mapping(target = "lastName", source = "lastName")
@Mapping(target = "email", source = "email")
void updateFields(@MappingTarget UserEntity entity, UserDTO dto);
然后,我不得不修改 UserService
class 中的 updateUser
方法。
@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
:
@Mapping(target = "targetFieldName", source = "sourceFieldName")
void updateFields(@MappingTarget UserEntity entity, UserDTO dto);
此注解确保不会创建新对象,它会维护原始对象,只是更新所需的字段。
我看到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
之间的(后面会提到)。
@Getter
@Setter
@ToString
public class UserDTO implements Serializable {
private static final long serialVersionUID = -2583091281719120521L;
// ------------------------------------------------------
// Attributes
// ------------------------------------------------------
private Long id; // actual id from the database table
private String userId; // public user id
private String firstName;
private String lastName;
private String email;
private String password;
private String encryptedPassword; // in the database we store the encrypted password
}// end of class
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
来return一条记录到客户端应用。
@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
上面的对象用于映射。现在,我将向您展示 UserController
和 UserService
中的代码。此问题发生在 UserService
class.
@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
UserService
用于实现应用的业务逻辑。
@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;
}
}
很可能是我做错了什么。
您认为问题的发生是因为我没有构造函数吗?通常 Java 自动创建无参数构造函数,如果我们不实现的话。
我添加了下面的图片,这样我就可以演示我想要做什么。也许这个流程可以提供帮助。
最有可能的方法 mapDtoToEntity
应该接受 2 个属性 以便将 UserDTO
映射到 UserEntity
。例如:
userMapper.mapDtoToEntity(userDto, userEntity);
public UserEntity mapDtoToEntity(UserDTO userDto, UserEntity userEntity) {
if( userDto == null ) {
return null;
}
// set ONLY the attributes of the UserEntity that were assigned
// to the UserDTO by the UserRequestModel. In the current case only 3 attributes
// return the updated UserEntity
}
谢谢Sergio Lema!!!我在我的代码中添加了您的修改,一切正常!
更新版本
我必须将以下方法添加到 UserMapper
class。
@Mapping(target = "firstName", source = "firstName")
@Mapping(target = "lastName", source = "lastName")
@Mapping(target = "email", source = "email")
void updateFields(@MappingTarget UserEntity entity, UserDTO dto);
然后,我不得不修改 UserService
class 中的 updateUser
方法。
@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
:
@Mapping(target = "targetFieldName", source = "sourceFieldName")
void updateFields(@MappingTarget UserEntity entity, UserDTO dto);
此注解确保不会创建新对象,它会维护原始对象,只是更新所需的字段。