"Nothing is lost, nothing is created, everything is transformed" _Lavoisier
J-Metamorphosis is the java version of Metamorphosis, an utility library to ease conversions of objects, provided as java, javascript and NestJS as well.
J-Metamorphosis is based on spring conversion service that helps you to create converters from/to DTO/Entity or between DTOs.
It creates the spring conversion service and registers all your converters. For DTO/Entity converters, let inherit from the base classes (DefaultConverterToDTO
, DefaultConverterToEntity
) to take advantage of automatically entity retrieve from DB.
Furthermore, if you need a mapping between DTO and Entity fields, for example to create JPA specifications in order to add 'where clause' to HQL queries, you can use mapping annotations (@MappedOnEntity
, @MappedOnEntityField
) and FieldMapingHelper
to get the mapping between <dto entity field, entity field path>.
Add to your pom.xml: metamorphosis-core
or metamorphosis-jpa
if you use jpa.
<dependency>
<groupId>it.fabioformosa</groupId>
<artifactId>metamorphosis-core</artifactId>
<version>3.0.0</version>
</dependency>
or
<dependency>
<groupId>it.fabioformosa</groupId>
<artifactId>metamorphosis-jpa</artifactId>
<version>3.0.0</version>
</dependency>
add @EnableMetamorphosisConversions
to your spring boot config class
@Configuration
@EnableMetamorphosisConversions(basePackages = { "your.package" })
@ComponentScan(basePackages = { "your.package" })
@EntityScan(basePackages = { "your.package" })
@EnableJpaRepositories(basePackages = { "your.package" })
public class MetamorphosisExampleConfig {
}
You can extend AbstractBaseConverter
to write a generic converter: class to class.
@Component
public class CustomerToConsumerConverter extends AbstractBaseConverter<Customer, Consumer>{
@Override
protected void convert(Customer source, Consumer target){
target.setId(source.getId())
target.setName(source.getName());
...
}
@Override
protected T createOrRetrieveTarget(Customer source){
return new Consumer();
}
}
If you have to write converter from/to entity, so you can take advantage of the following abstract classes.
You can extend:
-
AbstractBaseConverterToEntity
: The Entity is retrieved from DB Repository looking for the ID specified by the DTO. Otherwise a new entity is instantiated.@Component public class ItemDTOToItemEntity extends AbstractBaseConverterToEntity<ItemDTO, ItemEntity> { private ItemJpaRepository itemJpaRepository; @Override protected JpaRepository getRepository() { return itemJpaRepository; } @Override protected void convert(ItemDTO source, ItemEntity target) { target.setName(source.getName); target.setDate(source.getDate); ... } }
-
DefaultConverterToEntity
: AsAbstractBaseConverterToEntity
, but it automatically converts fields that match by fieldname. It usesBeanUtils.copyProperties
@Component public class ItemDTOToItemEntity extends DefaultConverterToEntity<ItemDTO, ItemEntity> { private ItemJpaRepository itemJpaRepository; @Override protected JpaRepository getRepository() { return itemJpaRepository; } @Override protected void convert(ItemDTO source, ItemEntity target) { ItemEntity target = super.convert(source, target); //for not-matching fields by fieldname target.setField(...); } }
You can extend:
-
AbstractBaseConverterToDTO
: A new DTO instance (target obj) is automatically created calling the default constructor@Component public class ItemToItemDTO extends AbstractBaseConverterToDTO<ItemEntity, ItemDTO> { @Override protected void convert(ItemEntity source, ItemDTO target) { target.setName(source.getName); target.setDate(source.getDate); ... } }
-
DefaultConverterToDTO
: AsAbstractBaseConverterToDTO
, but it automatically converts fields that match by fieldname. It usesBeanUtils.copyProperties
@Component public class ItemToItemDTO extends DefaultConverterToDTO<ItemEntity, ItemDTO> { @Override protected void convert(ItemEntity source, ItemDTO target) { ItemEntity target = super.convert(source, target); //for not-matching fields by fieldname target.setField(...); } }
@Component
public class SampleService {
@Resource
private ConversionService conversionService;
public SimpleDTO saveEntity(SimpleDTO simpleDTO) {
SimpleEntity simpleEntity = conversionService.convert(simpleDTO, SimpleEntity.class);
... <use entity> ...
return conversionService.convert(simpleEntity, SimpleDTO.class);
}
}
Usually DTOs are used to extract data from persistence layer. From DTO, it needs to build hibernate query criteria in order to apply filtering (e.g. filterable grids). For example, if you have in your frontend a filterable grid, your REST controller will receive a DTO with the values of the filter applied from the user. Your API must project this filter in JPA condition on your entity.
So, you need a mapping between DTO fieldnames and entity fieldnames.
it.fabioformosa.metamorphosis.mappers.FieldMappingHelper
helps you to get a map<String, String>
that binds a DTO fieldname to an entity fieldname (with dot notation, e.g. 'dto.foo' -> 'entity.foo').
To accomplish it, you should annotate your DTO with the annotation it.fabioformosa.metamorphosis.mappers.MappedOnEntity
and DTO fields with it.fabioformosa.metamorphosis.mappers.MappedOnEntityField
, so you can invoke FieldMappingHelper.getMappingByDTO(DTO.class)
to get mappings data you need to build the right JPA queries.
e.g.
@Entity
public class AuditedItemEntity {
private Long id;
private String name;
private String category;
private Location location;
private Location targetLocation;
private String itemTypeLabel;
private LocalDateTime createdDate;
private String creationUser;
private LocalDateTime lastModifyDate;
private String lastModifyUser;
... (getters and setters)...
}
@MappedOnEntity(AuditedItemEntity.class)
public class AuditItemDTO {
private Long id;
@MappedOnEntityField
private String name;
@MappedOnEntityField(entityField = "category")
private String categoryName;
@MappedOnEntityField(entityField = "location", cascade = true)
private LocationDTO locationDTO;
@MappedOnEntityField(entityField = "targetLocation.name")
private String targetLocationName;
@MappedOnEntityField(entityField = "itemTypeLabel", innerDtoField = "enumLabel", cascade = false)
private EnumDTO itemType;
@MappedOnEntityField(cascade = true, concatOnCascade = false)
private AuditDTO auditDTO;
...(getters and setters)
}
Look at the CHANGELOG to read all breaking changes before to bump metamorphosis version.
For further details about how to use j-metamorphosis
, look at this sample project.
Chameleon in this README file is a picture of ph. Nandhu Kumar (pexels.com)