MapStruct set List as field - java

I'm using Map Struct with Lombok for mapping DTO and Entity back and forth but occurred on a case:
#Mapper(uses = {RoleMapper.class})
public interface UserMapper {
UserDto userToUserDto(User user);
default User signUpRequestDtoToUser(SignUpRequestDto dto) {
return User.builder()
.roles(dto.roleIds.stream().map(id -> Role.builder().id(id).build()).collect(Collectors.toList()))
.username(dto.getUsername())
.password(dto.getPassword())
.isEnabled(dto.getIsEnabled())
.build();
}
default UserFilter toUserFilter(UserFilterDto dto) {
return UserFilter.builder()
.isEnabled(dto.getIsEnabled())
.username(dto.getUsername())
.roles(
Objects.nonNull(dto.getRoleIds())
? dto.getRoleIds().stream().map(id -> Role.builder().id(id).build()).collect(Collectors.toList())
: Collections.emptyList())
.build();
}
}
Into other cases, I'm using annotation like this: #Mapping(target = "advisor.id", source = "advisorId") for create objects from id. It's work where parent contains one instance. But User and UserFilter has List<Role> as field.
How to replace the default method with annotation?

From what I can see in your example, I assume you can use annotations in this case as well: just create a method to map between RoleId and Role and Mapstruct will implement this method and call it method automatically when trying to map the collections of those models:
#Mapping(source = "id", target = "id")
Role mapRoleIdToRole(RoleId roleId);

Related

How to convert Optional<Entity> to Optional<EntityDTO> in Spring JPA?

I am new in Spring and although I can convert domain entities as List<Entity>, I cannot convert them properly for the the Optional<Entity>. I have the following methods in repository and service:
EmployeeRepository:
#Query(value = "SELECT ...")
Optional<Employee> findByUuid(#Param(value = "uuid") final UUID uuid);
EmployeeService:
#Override
#LogExecution
#Transactional(readOnly = true)
public Optional<EmployeeDTO> findByUuid(UUID uuid) {
Optional<Employee> employee = employeeRepository.findByUuid(uuid);
return employee
.stream()
.map(EmployeeDTO::new)
// .orElse(null);
//.findFirst(); /// ???
}
My questions:
1. How should I convert Optional<Employee> to Optional<EmployeeDTO> properly?
2. Does Spring JPA collect the fields in the SELECT clause and map them in the service method to the corresponding DTO by matching their names? If so, does it maintain the naming e.g. employee_name to employeeName in database table and domain model class?
The mapping that happens between the output of employeeRepository#findByUuid that is Optional<Employee> and the method output type Optional<EmployeeDTO> is 1:1, so no Stream (calling stream()) here is involved.
All you need is to map properly the fields of Employee into EmployeeDTO. Handling the case the Optional returned from the employeeRepository#findByUuid is actually empty could be left on the subsequent chains of the optional. There is no need for orElse or findFirst calls.
Assuming the following classes both with all-args constructor and getters:
class Employee {
private final long id;
private final String firstName;
private final String lastName;
}
class EmployeeDTO {
private final long id;
private final String name;
private final String surname;
}
... you can perform this. Nothing else than finding a way to create EmployeeDTO from Employee's fields is needed. If the Optional returned from the employeeRepository is returned, no mapping happens and an empty Optional is returned.
#Override
#LogExecution
#Transactional(readOnly = true)
public Optional<EmployeeDTO> findByUuid(UUID uuid) {
return employeeRepository
.findByUuid(uuid) // Optional<Employee>
.map(emp -> new EmployeeDTO( // Optional<EmployeeDTO>
emp.getId(), // .. id -> id
emp.getFirstName(), // .. firstName -> name
emp.getLastName())); // .. lastName -> surname
}
Note: For Employee -> EmployeeDTO mapping I recommend picking one of these:
Create a constructor accepting Employee in EmployeeDTO allowing to map with .map(EmployeeDTO::new) (drawback: creates a dependency).
Just map with getters/setters.
Use a mapping framework such as MapStruct or any other.
There are multiple options to map your entity to a DTO.
Using projections: Your repository can directly return a DTO by using projections. This might be the best option if you don't need the entity at all. You can find everything about projections here https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
Using a library like mapstruct or modelmapper to generate your mapping code
Add a constructor or static factory method to your DTO. Something like
class EmployeeDTO {
// fields here ...
public static EmployeeDTO ofEntity(Employee entity) {
var dto = new EmployeeDTO();
// set fields
return dto;
}
}
And call employee.map(EmployeeDTO::ofEntity) in your service.

LazyInitializationException with Mapstruct because of cyclic issue

I have a development project using Spring Data JPA and MapStruct to map between Entities and DTOs. Last week I decided it was time to address the FetchType.EAGER vs LAZY issue I have postponed for some time. I choose to use #NamedEntityGraph and #EntityGraph to load properties when needed. However I am stuck with this LazyInitializationExeption problem when doing the mapping from entity to dto. I think I know where this happens but I do not know how to get passed it.
The code
#NamedEntityGraph(name="Employee.full", ...)
#Entity
public class Employee {
private Set<Role> roles = new HashSet<>();
}
#Entity
public class Role {
private Set<Employee> employees = new HashSet<>();
}
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
#EntityGraph(value = "Employee.full")
#Override
Page<Employee> findAll(Pageable pageable);
}
#Service
public class EmployeeService {
public Page<EmployeeDTO> findAll(PageRequest pageRequest) {
Page<Employee> employees = repository.findAll(pageRequest); // ok
Page<EmployeeDTO> dtos = employees.map(emp -> mapper.toDTO(emp, new CycleAvoidMappingContext()); // this is where the exception happens
return dtos;
}
}
// also there is EmployeeDTO and RoleDTO classes mirroring the entity classes
// and there is a simple interface EmployeeMapper loaded as a spring component
// without any special mappings. However CycleAvoidingMappingContext is used.
I have tracked down the LazyInitializationException to happen when the mapper tries to map the roles dependency. The Role object do have Set<Employee> and therefore there is a cyclic reference.
When using FetchType.EAGER new CycleAvoidingMappingContext() solved this problem, but with LAZY this no longer works.
Does anybody know how I can avoid the exception and at the same time get my DTOs mapped correctly?
The problem is that when the code returns from findAll the entities are not managed anymore. So you have a LazyInitializationException because you are trying, outside of the scope of the session, to access a collection that hasn't been initialized already.
Adding eager make it works because it makes sure that the collection has been already initialized.
You have two alternatives:
Using an EAGER fetch;
Make sure that the entities are still managed when you return from the findAll. Adding a #Transactional to the method should work:
#Service
public class EmployeeService {
#Transactional
public Page<EmployeeDTO> findAll(PageRequest pageRequest) {
Page<Employee> employees = repository.findAll(pageRequest);
Page<EmployeeDTO> dtos = employees.map(emp -> mapper.toDTO(emp, new CycleAvoidMappingContext());
return dtos;
}
}
I would say that if you need the collection initialized, fetching it eagerly (with an entity graph or a query) makes sense.
Check this article for more details on entities states in Hibernate ORM.
UPDATE: It seems that this error happens because Mapstruct is converting the collection even if you don't need it in the DTO.
In this case, you have different options:
Remove the field roles from the DTO. Mapstruct will ignore the field in the entity because the DTO doesn't have a field with the same name;
Create a different DTO class for this specific case without the field roles;
Use the #Mapping annotation to ignore the field in the entity:
#Mapping(target = "roles", ignore = true)
void toDTO(...)
or, if you need the toDTO method sometimes
#Mapping(target = "roles", ignore = true)
void toSkipRolesDTO(...) // same signature as toDTO

Is it possible to map with Mapstruct an attribute of object B to attribute of object A_DTO, when object A contains a reference to object B?

According to Mapstruct documentation it is possible to map to DTO an object (object A) that contains another object (object B) by defining a mapping method for the referenced object (object B). But what if I need to map only attribute of that object (object B) and not the whole object?
Describing problem -
I am studing the Spring Boot and here is my project - https://github.com/Alex1182-St/java-spring-jpa-postgresql
With purpose of security I need to map my AppUserEntity to AppUserDetailsDTO (implements UserDetails) and especially I need to map the name from the attribute private Set<RoleEntity> roles of my AppUserEntity to the private Collection<GrantedAuthority> authorities of my AppUserDetailsDTO
With Kotlin it is easy (authorities = roles.map { it.name }):
fun AppUserEntity.toAppUserDetailsDTO() = AppUserDetailsDTO(
id = id,
username = appUserLogin,
password = appUserPassword,
authorities = roles.map { it.name },
isEnabled = isEnabled,
isAccountNonLocked = isAccountNonLocked,
isAccountNonExpired = isAccountNonExpired,
isCredentialsNonExpired = isCredentialsNonExpired
)
But How to do it with Java and Mapstruct?
On Mapstruct a method can be used for the mapping on the annotations using the expression property on the annotation: expression = "java( yourJavaCodeHere )".
Your mapper would look like:
#Mapper(componentModel = "spring")
public abstract class AppUserDetailsDtoMapper {
#Mappings({
#Mapping(target = "username", source = "appUserLogin"),
#Mapping(target = "password", source = "appUserPassword"),
#Mapping(target = "authorities", expression = "java( mapAuthorities(user.getRoles()) )")
})
public abstract AppUserDetailsDTO toAppUserDetailsDTO(AppUserEntity user);
protected Collection<GrantedAuthority> mapAuthorities(Set<RoleEntity> roles) {
// Map the authorities here
}
}

How to create or enhance a custom annotation that uses mapstruct

Mapsturct has #Mapping annotation with predefined attributes
eg: #Mapping(source="", target="", qualifiedByName="") , what if i want to add another attribute it and use it to compute the logic
eg: #Mapping(source="", target="", qualifiedByName="" , version="")
I would like to pass the version number to it and depending on the version, it would set the target from source.
I tried to create a custom annotation and use #Mapping in it but didnt help
#CustomMapping(version = "1.0", mapping = #Mapping(source = "", target = "", qualifiedByName=""))
Why don't you just define a 2nd distinct annotation to be used alongside #Mapping?
#Mapping(source="", target="", qualifiedByName="")
#MappingVersion("1.0")
CarDto carToCarDto(Car car);
If your concern is that you want some kind of enforcement that version is always present when #Mapping is used then you could trivially write an annotation processor which does this at compile-time.
Based on the comments in the question I understand a bit more what needs to be done. Basically based on some input parameter different mapping methods need to be created.
Not sure how complex your logic is. However, what you can do is to create your own annotation processor that would be able to create MapStruct mappers.
Let's imagine that you have
#CustomMapper
public interface MyMapper {
#CustomMapping(version = "1.0", mappings = {
#Mapping(source = "numberOfSeats", target = "seatCount", qualifiedByName="")
})
#CustomMapping(version = "2.0", mappings = {
#Mapping(source = "numberOfSeats", target = "seats", qualifiedByName="")
})
CarDto map(Car car, String version);
}
So your annotation processor would need to handle CustomMapper.
The processor would also generate the different MapStruct versioned interfaces.
So:
#Mapper
public interface MyMapperV1 {
#Mapping(source = "numberOfSeats", target = "seatCount", qualifiedByName="")
CarDto map(Car car)
}
#Mapper
public interface MyMapperV2 {
#Mapping(source = "numberOfSeats", target = "seats", qualifiedByName="")
CarDto map(Car car)
}
And additionally an implementation of MyMapper. That looks like:
public class MyMapperImpl {
protected MyMapperV1 myMapperV1 = Mappers.getMapper(MyMapperV1.class):
protected MyMapperV2 myMapperV2 = Mappers.getMapper(MyMapperV2.class):
public CarDto map(Car car, String version) {
if ("1.0".equals(version)) {
return myMapperV1.map(car);
} else {
return myMapperV2.map(car);
}
}
}
Basically the goal is to have your processor generate the interfaces that would be picked up by MapStruct in the same compilation round. This is possible with annotation processing.
Another option is to write the MapStruct mappers on your own and in the caller place pick the one which is appropriate for the version. This might be simpler actually.

How to call MapStruct method from interface in java stream

I recently started using the MapStruct mapping tool in a project. In the past, for mapping DTO -> Entity and vice versa I used custom mapper like:
public static CustomerDto toDto(Customer customer) {
return isNull(customer)
? null
: CustomerDto.builder()
.id(customer.getId())
.name(customer.getName())
.surname(customer.getSurname())
.phoneNumber(customer.getPhoneNumber())
.email(customer.getEmail())
.customerStatus(customer.getCustomerStatus())
.username(customer.getUsername())
.NIP(customer.getNIP())
.build();
}
In case when I was trying to get one single Optional object after all I was able to map my entity to dto in the following way:
public Optional<CustomerDto> findOneById(final long id) {
return customerRepository.findById(id).map(CustomerMapper::toDto);
}
Currently, as I mentioned before I am using mapStruct and the problem is that my mapper it's, not class, it's the interface like:
#Mapper
public interface CommentMapper {
#Mappings({
#Mapping(target = "content", source = "entity.content"),
#Mapping(target = "user", source = "entity.user")
})
CommentDto commentToCommentDto(Comment entity);
#Mappings({
#Mapping(target = "content", source = "dto.content"),
#Mapping(target = "user", source = "dto.user")
})
Comment commentDtoToComment(CommentDto dto);
}
I want to know if it possible to use somehow this interface method in stream gentle to map my value without wrapping values like:
public Optional<CommentDto> findCommentById(final long id) {
Optional<Comment> commentById = commentRepository.findById(id);
return Optional.ofNullable(commentMapper.commentToCommentDto(commentById.get()));
}
Thanks for any help.
Access the mapper like:
private static final YourMapper MAPPER = Mappers.getMapper(YourMapper.class);
final Optional<YourEntity> optEntity = entityRepo.findById(id);
return optEntity.map(MAPPER::toDto).orElse(null);
Basically we do a similar thing with enumerations
#Mapping(target = "type", expression = "java(EnumerationType.valueOf(entity.getType()))")
you can define java expressions in your #Mapping annotation
#Mapping(target = "comment", expression = "java(commentMapper.commentToCommentDto(commentRepository.findById(entity.commentId).orElse(null)))"
Otherwise you should be able to make use of a
class CommentMapper { ... }
which you automatically can refer with
#Mapper(uses = {CommentMapper.class})
your implementation will detect the commentEntity and Dto and will automatically use the CommentMapper.
A MapStruct mapper is workling like: Shit in Shit out, so remember your entity needs the commentEntity so the dto can has the commentDto.
EDIT
2nd solution could be using:
#BeforeMapping
default void beforeMappingToDTO(Entity source, #MappingTarget Dto target) {
target.setComment(commentMapper.commentToCommentDto(commentRepository.findById(entity.commentId).orElse(null)));
}
#Spektakulatius answer solved a problem.
To reach a goal I made a few steps:
First of all, I created an object of my mapper to use it in a java stream:
private CommentMapper commentMapper = Mappers.getMapper(CommentMapper.class);
In the last step I used my object commentMapper like:
public Optional<CommentDto> findCommentById(final long id) {
return commentRepository.findById(id).map(CommentMapper.MAPPER::commentToCommentDto);
}
After all that completely gave me a possibility to use my custom MapStruct mapper in stream.

Categories

Resources