Mainly used to transfer data between the client and the server
Does not contain any business logic
Only contains getter and setter methods
Normally created as POJOs (Plain Old Java Object)
baeldung
Here User and Role are two different entities
They are mapped to a DTO named UserDTO via Mapper
Why do we use DTOs? (Why not only entities?)
Entity?
Classes that are mapped to actual DBs
Can contain business logics
1. Hide sensitive information
2. Separate responsibilities and roles - Suppose we have a User entity with field age
(IF we do not use DTO)
The business logic has changed - now we retrieve age from the government's database, not users database.
Now age has to be removed from the User entity as it is not in users database
Everything in frontend views that refer to User's age must be changed
(IF we use DTO)
We have a user DTO which contains age
Since we do not have age in users database, we can remove it from User entity
We can map government database's age to user DTO so that it can fetch age
Any field added can follow this structure - The views do not have to be updated
3. Reducing network overhead
No need to send the complete domain objects - we can be more efficient when dealing with large or complex data structures
Common transfer from DTO to Entity (gnidinger.tistory.com)
Mapping JPA Entity to DTO
3 common ways : Java code mapper, ModelMapper, Mapstruct
Java Code
public class UserMapper {
public static UserDto mapUserToUserDto(User user) {
UserDto userDto = new UserDto(
user.getId(),
user.getFirstName(),
user.getLastName(),
user.getEmail()
);
return userDto;
}
public static User mapUserDtoToUser(UserDto userDto) {
User user = new User(
userDto.getId(),
userDto.getFirstName(),
userDto.getLastName(),
userDto.getEmail()
);
return user;
}
}
Advantages
Nearly zero impact in performance as it only requires method calls
Disadvantages
Vulnerable to human errors
Not useful for advanced, complex mappings
ModelMapper
ModelMapper mapper = new ModelMapper();
// If we have setter functions
User user = mapper.map(userDto, User.class);
Advantages
More simple code (for simple mappings)
Disadvantages
Relies on reflection and determines the mapping rules at runtime, which makes it much slower than other libraries
More complex code for complex mappings
MapStruct
// Interface
@Mapper
public interface AutoUserMapper {
AutoUserMapper MAPPER = Mappers.getMapper(AutoUserMapper.class);
// Factory function will automatically create MAPPER instance
@Mapping(source = "email", target = "emailAddress")
UserDto mapUserToUserDto(User user);
@Mapping(source = "emailAddress", target = "email")
User mapUserDtoToUser(UserDto userDto);
// We can map fields with different names by @Mapping
}
// Usage
AutoUserMapper userMapper = AutoUserMapper.MAPPER;
User user = userMapper.mapUserDtoToUser(userDto);
Advantages
Mapping code is generated at compilation time, which is highly optimized and fast
Relies on explicit mapping interfaces and annotations, which allows us to validate mappings in compile time