martes, 24 de mayo de 2022

MapStruct walking to map between java beans 1

MapStruct walking to map between java beans

https://github.com/jalbertomr/mapStructDemo

- Maven setup and Dependencies
- Mapping Fields
- Checking the Autocode Generation
- Integrating Title
- Mapping Values
- @Tests

This library helps to map fields between java POJOs creating code automatically for the mapper.

The code generation at compiling time requires maven plugins in order to allow the code generation.


Maven setup and Dependencies

<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>11</source>
<target>11</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>

Mapping Fields

Having to POJOs 

Customer
@Data
public class Customer {
private long id;
private String firstName;
private String lastName;
//private Title title;
private LocalDate dateOfBirth;
private BigDecimal creditScore;
private CustomerType customerType;
private Address address;
private Date creation;
}
CustomerDto
@Data
public class CustomerDto {
public long customerId;
public String firstName;
public String lastName;
public double creditScore;
public String dateOfBirth;
public LocalDate creation;
public String title;
public AddressDto address;
public String fullName;
}




Checking the auto code generation


@Mapper
public interface CustomerMapper {
}

When compile is generated the class CustomerMapperImpl in target.generated-sources in this case not map anything because nothing has been coded in CustomerMapper

import javax.annotation.processing.Generated;

@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-05-24T10:57:51-0500",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 11.0.8 (Oracle Corporation)"
)
public class CustomerMapperImpl implements CustomerMapper {
}
Lets code the map function in the mapper

@Mapper
public interface CustomerMapper {

CustomerDto customerToDto(Customer customer);

}
When compile the code generates the CustomerMapperImpl, based on the name of the fields, mapstruct makes the necessary convertions between fields,  even creates the Address mapper. if mapStruct finds a field that does not know how to convert, MapStruct will indicate that a conversion is needed to be coded. The fields that has not the same name, must by indicated by @Mapping annotation specifyng the source field and the target field. 

@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-05-24T11:53:23-0500",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 11.0.8 (Oracle Corporation)"
)
public class CustomerMapperImpl implements CustomerMapper {

@Override
public CustomerDto customerToDto(Customer customer) {
if ( customer == null ) {
return null;
}

CustomerDto customerDto = new CustomerDto();

customerDto.setFirstName( customer.getFirstName() );
customerDto.setLastName( customer.getLastName() );
if ( customer.getCreditScore() != null ) {
customerDto.setCreditScore( customer.getCreditScore().doubleValue() );
}
if ( customer.getDateOfBirth() != null ) {
customerDto.setDateOfBirth( DateTimeFormatter.ISO_LOCAL_DATE.format( customer.getDateOfBirth() ) );
}
if ( customer.getCreation() != null ) {
customerDto.setCreation( LocalDateTime.ofInstant( customer.getCreation().toInstant(), ZoneOffset.UTC ).toLocalDate() );
}
customerDto.setAddress( addressToAddressDto( customer.getAddress() ) );

return customerDto;
}

protected AddressDto addressToAddressDto(Address address) {
if ( address == null ) {
return null;
}

AddressDto addressDto = new AddressDto();

addressDto.setStreet( address.getStreet() );
addressDto.setCity( address.getCity() );

return addressDto;
}
}
Also, to avoid bolierplate of the addressMapper generated into the CustomerMapper, just specifying to the use existing AddressMapper  


@Mapper(uses=AddressMapper.class)
public interface CustomerMapper {

@Mapping(target = "customerId", source = "id")
@Mapping(target = "dateOfBirth", dateFormat = "dd.MM.yyyy")
@Mapping(target = "fullName", expression = "java(customer.getFirstName() + \" \" + customer.getLastName())")
CustomerDto customerToDto(Customer customer);

default String toString(Title title){
return title.getName();
}
}
The generated CustomerMapperImpl now includes the id, fullName , format the date, and use the AddressMapper.

@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-05-24T12:17:58-0500",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 11.0.8 (Oracle Corporation)"
)
public class CustomerMapperImpl implements CustomerMapper {

private final AddressMapper addressMapper = Mappers.getMapper( AddressMapper.class );

@Override
public CustomerDto customerToDto(Customer customer) {
if ( customer == null ) {
return null;
}

CustomerDto customerDto = new CustomerDto();

customerDto.setCustomerId( customer.getId() );
if ( customer.getDateOfBirth() != null ) {
customerDto.setDateOfBirth( DateTimeFormatter.ofPattern( "dd.MM.yyyy" ).format( customer.getDateOfBirth() ) );
}
customerDto.setFirstName( customer.getFirstName() );
customerDto.setLastName( customer.getLastName() );
if ( customer.getCreditScore() != null ) {
customerDto.setCreditScore( customer.getCreditScore().doubleValue() );
}
if ( customer.getCreation() != null ) {
customerDto.setCreation( LocalDateTime.ofInstant( customer.getCreation().toInstant(), ZoneOffset.UTC ).toLocalDate() );
}
customerDto.setAddress( addressMapper.addressToDto( customer.getAddress() ) );

customerDto.setFullName( customer.getFirstName() + " " + customer.getLastName() );

return customerDto;
}
}
Notice that the addressMapper instantiation is made with Mappers.getMapper( AddressMapper.class)
if in the @Mapper is specifyed the componentModel ="spring" then addressMapper will be injected with @Autowired

Mapper with componentModel specyfied

@Mapper(uses=AddressMapper.class, componentModel = "spring")
public interface CustomerMapper {
... }
Generated with @Autowired
@Component
public class CustomerMapperImpl implements CustomerMapper {

@Autowired
private AddressMapper addressMapper;


 Integrating Title


CustomerMapper uses TitleMapper to map Title.String to String

@Mapper( uses=TitleMapper.class, componentModel = "spring")
public interface CustomerMapper {
CustomerMapper INSTANCE = Mappers.getMapper(CustomerMapper.class);

@Mapping(target = "customerId", source = "id")
@Mapping(target = "dateOfBirth", dateFormat = "dd.MM.yyyy")
@Mapping(target = "fullName", expression = "java(customer.getFirstName() + \" \" + customer.getLastName())")
CustomerDto customerToDto(Customer customer);

@Mapping(target = "id", source = "customerId")
@Mapping(target = "dateOfBirth", dateFormat = "dd.MM.yyyy")
Customer customerDtoToCustomer(CustomerDto customerDto);

default String toString(Title title){
return title.getName();
}
}
Uses TitleMapper

@Mapper
public interface TitleMapper {
@Mapping( source="title", target = "name")
Title strTitleToTitle(String title);
}
Code Generated


@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2022-05-26T19:07:18-0500",
comments = "version: 1.4.2.Final, compiler: javac, environment: Java 11.0.8 (Oracle Corporation)"
)
@Component
public class CustomerMapperImpl implements CustomerMapper {

@Autowired
private TitleMapper titleMapper;

@Override
public CustomerDto customerToDto(Customer customer) {
if ( customer == null ) {
return null;
}

CustomerDto customerDto = new CustomerDto();

customerDto.setCustomerId( customer.getId() );
if ( customer.getDateOfBirth() != null ) {
customerDto.setDateOfBirth( DateTimeFormatter.ofPattern( "dd.MM.yyyy" ).format( customer.getDateOfBirth() ) );
}
if ( customer.getCreditScore() != null ) {
customerDto.setCreditScore( customer.getCreditScore().doubleValue() );
}
if ( customer.getCreation() != null ) {
customerDto.setCreation( LocalDateTime.ofInstant( customer.getCreation().toInstant(), ZoneOffset.UTC ).toLocalDate() );
}
customerDto.setTitle( toString( customer.getTitle() ) );
customerDto.setAddress( addressToAddressDto( customer.getAddress() ) );

customerDto.setFullName( customer.getFirstName() + " " + customer.getLastName() );

return customerDto;
}

@Override
public Customer customerDtoToCustomer(CustomerDto customerDto) {
if ( customerDto == null ) {
return null;
}

Customer customer = new Customer();

customer.setId( customerDto.getCustomerId() );
if ( customerDto.getDateOfBirth() != null ) {
customer.setDateOfBirth( LocalDate.parse( customerDto.getDateOfBirth(), DateTimeFormatter.ofPattern( "dd.MM.yyyy" ) ) );
}
customer.setTitle( titleMapper.strTitleToTitle( customerDto.getTitle() ) );
customer.setCreditScore( BigDecimal.valueOf( customerDto.getCreditScore() ) );
customer.setAddress( addressDtoToAddress( customerDto.getAddress() ) );
if ( customerDto.getCreation() != null ) {
customer.setCreation( Date.from( customerDto.getCreation().atStartOfDay( ZoneOffset.UTC ).toInstant() ) );
}

return customer;
}

protected AddressDto addressToAddressDto(Address address) {
if ( address == null ) {
return null;
}

AddressDto addressDto = new AddressDto();

addressDto.setStreet( address.getStreet() );
addressDto.setCity( address.getCity() );

return addressDto;
}

protected Address addressDtoToAddress(AddressDto addressDto) {
if ( addressDto == null ) {
return null;
}

Address address = new Address();

address.setCity( addressDto.getCity() );
address.setStreet( addressDto.getStreet() );

return address;
}
}

Mapping Values


Defaul Value

@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

@Mapping(source = "fuelType", target="fuelType",defaultValue = "ELECTRIC")
CarDto carTOCarDto(Car car);
}

Mapping enum to enum types


Also a mapping of enum values can be done with mapStruct.


@Mapper(componentModel = "spring")
public interface StuffMapper {
StuffMapper INSTANCE = Mappers.getMapper( StuffMapper.class);

@ValueMappings({
@ValueMapping(source="CORROSIVE", target="POINTY"),
@ValueMapping(source="STICKY", target="SHORT"),
@ValueMapping(source="SALTY", target="FLAT"),
@ValueMapping(source= MappingConstants.ANY_REMAINING, target="LONG"),
@ValueMapping(source=MappingConstants.NULL, target="GREASY"),
})
public FurType slimeToFur(SlimeType slime);

@Mapping(source = "slimeType", target ="furtype" )
public FurryStuff slimyToFurry(SlimyStuff slimy);
}

With this example of mapping in @ValueMappings are mapped the correspondence of values. when null then GREASY value is assigned. When ANY REMAINING value on the source enum then the target will be LONG.

@Tests


The @Tests for StuffMapper using INSTANCE make the run faster than using @SpringBootTest in test class @Autowired to inject the mapper, and componentModel="spring" as parameter for @Mapper in the mapper. 


class StuffMapperTest {

@Test
void slimeToFurTest_CORROSIVE_to_POINTY() {
//given
SlimeType slime = SlimeType.CORROSIVE;
//when
FurType furType = StuffMapper.INSTANCE.slimeToFur( slime);
//then
Assertions.assertEquals(FurType.POINTY, furType);
}

@Test
void slimeToFurTest_NULL_to_GREASY() {
//given
SlimeType slime = null;
//when
FurType furType = StuffMapper.INSTANCE.slimeToFur( slime);
//then
Assertions.assertEquals(FurType.GREASY, furType);
}

@Test
void slimeToFurTest_ANYREMAINING_to_LONG() {
//given
SlimeType slime = SlimeType.SMELLY;
//when
FurType furType = StuffMapper.INSTANCE.slimeToFur( slime);
//then
Assertions.assertEquals(FurType.LONG, furType);
}

@Test
void slimyToFurryTest_CORROSIVE_to_POINTY() {
//given
SlimyStuff naOH = new SlimyStuff("NaOH", SlimeType.CORROSIVE);
//when
FurryStuff furryStuff = StuffMapper.INSTANCE.slimyToFurry(naOH);
//then
Assertions.assertEquals(FurType.POINTY, furryStuff.getFurtype());
}

@Test
void slimyToFurryTest_NULL_to_GREASY() {
//given
SlimyStuff naOH = new SlimyStuff("NaOH", null);
//when
FurryStuff furryStuff = StuffMapper.INSTANCE.slimyToFurry(naOH);
//then
Assertions.assertEquals(FurType.GREASY, furryStuff.getFurtype());
}

@Test
void slimyToFurryTest_ANYREMAINING_to_LONG() {
//given
SlimyStuff naOH = new SlimyStuff("NaOH", SlimeType.SMELLY);
//when
FurryStuff furryStuff = StuffMapper.INSTANCE.slimyToFurry(naOH);
//then
Assertions.assertEquals(FurType.LONG, furryStuff.getFurtype());
}
}

eot

No hay comentarios:

Publicar un comentario