using mapstruct how to map an object to a list of objects with spring component mappers - mapstruct

I am trying to map an object A to a list of object B.
I have a mapper which maps from object A to object B.
I have tried a number of different ways for example
Trying to create a list with one object A using 'expressions'
And 'qualifiedByName' but this does not work because I think
when you use expressions/qualifiedByName you can not use
Custom mappers ( I could be wrong here ?)
I also tried to call the mapper from the #aftermapper method using
The ‘mappers.getMapper’ to get a handle on the target mapper
But I found that the spring beans used in the mapper
Where not being populated. Mapping in the aftermapping makes
Sense in that I can call the target mapper with the source
And then add the target to the list. So I am hoping that there
is another Way to get a handle on the mapper component from my mapper.
All my mappers use
#Mapper(componentModel="spring",
All suggestions are welcome
Below is an code sample showing the problem.
Regards,
Declan
// SPECIESTOLOGSPECY.JAVA
// From this mapper I want to call SpecyToLogDeclarationMapperApi mapper
// to map ‘species’ to ‘logdeclarations’ which is a list of logdeclaration
// you can see want I am trying to do in the aftermapping method
// where I map species to logdeclaration and then put this into a list
// unfortunately I need other mapping components (ConfigMapperFromCode & SpecyToFishDeclarationMapperApi)
// to be autowired into SpecyToLogDeclarationMapperApi and this is not happening.
// is there another way to get a handle to the spring managed
// SpecyToLogDeclarationMapperApi mapper ?
#Mapper(componentModel="spring",
uses = {
ConfigMapperFromCode.class,
GeoInfoMapper.class,
SpecyToLogDeclarationMapperApi.class
})
public interface SpeciesToLogSpecy {
SpecyToLogDeclarationMapperApi MAPPER = Mappers.getMapper(SpecyToLogDeclarationMapperApi.class);
#Mappings(
{
#Mapping(target="createdate", expression = "java(java.sql.Timestamp.valueOf(java.time.LocalDateTime.now()))"),
#Mapping(target="speciesid", qualifiedByName={"ConfigMapperFromCode", "speciesIdFromCodeAsDecimal"}, source = "species.speciesCode"),
#Mapping(target="unitweight", constant = "1"),
#Mapping(target = "inactiveind", constant = "N"),
#Mapping(target = "unitdefaultind", constant = "Y"),
#Mapping(target = "sectiontypeid", expression = "java(new BigDecimal(ie.gov.agriculture.fisheries.logsheet.mapping.constants.MappingConstants.LOG_SPECIES_SECTION_TYPE_SHEETDECLARATION))"),
#Mapping(target = "unituomid", expression = "java(new BigDecimal(ie.gov.agriculture.fisheries.logsheet.mapping.constants.MappingConstants.LOGSHEET_CATCHUNITS_KG))"),
#Mapping(target="catchtypeid", qualifiedByName={"ConfigMapperFromCode", "returnCatchTypeId"}, source = "species.spType"),
#Mapping(target="legalfishsizetypeid", qualifiedByName={"ConfigMapperFromCode", "legalFishSizeTypeIdFromCode"}, source = "species.fishSizeClass"),
#Mapping(target="presenttypeid", qualifiedByName={"ConfigMapperFromCode", "presentationTypeIdFromCode"}, source = "species.presentation.presentationType"),
//#Mapping(target="logdeclarations", source = "species")
}
)
Logspecy speciesToLogspecy(Species species, #Context ExtraFields extraFields);
#AfterMapping
default void afterMap(#MappingTarget Logspecy logspecy, #Context ExtraFields extraFields){
Logdeclaration logDeclaration = MAPPER.SpeciesToLogDeclarations(species, extraFields);
List<Logdeclaration> logdeclarations = new ArrayList<Logdeclaration>();
logdeclarations.add(logDeclaration);
logSpecy.setLogdeclarations(logdeclarations);
{
// SPECYTOLOGDECLARATIONMAPPERAPI.JAVA
#Mapper(componentModel="spring",
uses = {
ConfigMapperFromCode.class,
SpecyToFishDeclarationMapperApi.class
}
)
public interface SpecyToLogDeclarationMapperApi {
#Mappings(
{
#Mapping(target="createdate", expression = "java(java.sql.Timestamp.valueOf(java.time.LocalDateTime.now()))"),
#Mapping(target="geartypeid", qualifiedByName={"ConfigMapperFromCode", "gearIdFromCode"}, source = "species.gearType"),
#Mapping(target="fishcount", source = "species.qty"),
#Mapping(target = "inactiveind", constant = "N"),
#Mapping(target="packagetypeid", qualifiedByName={"ConfigMapperFromCode", "packagingTypeIdFromCode"}, source = "species.presentation.packaging"),
#Mapping(target="packagecount", source = "species.presentation.pkgunit"),
#Mapping(target="avgpackageweight", source = "species.presentation.pkgUnitWeight"),
#Mapping(target="conversionfactor", source = "species.presentation.convFactor"),
#Mapping(target="fishdeclaration", source = "species.geoInfo")
}
)
Logdeclaration SpeciesToLogDeclarations (Species species, #Context ExtraFields extraFields);

The problem is that you are trying to map Species to List<Logdeclaration> and MapStruct cannot find such mapping method. In order to make it work you can add the following methods to your SpecyToLogDeclarationMapperApi:
default List<Logdeclaration> SpeciesToLogDeclarationsToList(Species species, #Context ExtraFields extraFields) {
if ( species == null ) {
return null;
}
Logdeclaration logDeclaration = SpeciesToLogDeclarations(species, extraFields);
List<Logdeclaration> logdeclarations = new ArrayList<Logdeclaration>();
logdeclarations.add(logDeclaration);
return logdeclarations;
}
This are some additional things, that I think you can do to improve your code and use MapStruct "more correctly":
You will need to remove the usage of Mappers#getMapper(Class) when the component model is not the default
If you don't want to use FQN in your expressions, you can use Mapper#imports and MapStruct will import them in the implementation.
When you have a single source parameter you don't have to use it's name in the mapping
e.g.
#Mapping(target="fishcount", source = "species.qty")
can be
#Mapping(target="fishcount", source = "qty")

Related

jooq deserialize a polymorphic class

I am using JOOQ to manipulate the database,now i have a problem.
There is a polymorphic class OrderEntry
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type", visible = true)
#JsonSubTypes(value = {
#JsonSubTypes.Type(value = ReissueOrderEntry.class, name = "reissue"),
#JsonSubTypes.Type(value = RawOrderEntry.class, name = "raw"),
#JsonSubTypes.Type(value = FreebieOrderEntry.class, name = "freebie"),
#JsonSubTypes.Type(value = ReplaceOrderEntry.class, name = "replace")
})
public class OrderEntry extends OrderObject {
String type;
}
It will be deserialized into different objects according to the field 'type'.
But in jooq's deserialization it will only be deserialized as OrderEntry.
code
How can i solve it?
I'm assuming you're trying to use the built-in ConverterProvider logic that makes use of Jackson, e.g. when writing things like:
record.into(OrderEntry.class);
jOOQ loads Jackson from the classpath without any additional modules / plugins loaded. If you wish to use additional plugins, then you'll have to roll your own ConverterProvider, which implements loading additional plugins.

Java: Mapping DTOs hierarchy using MapStruct

I have Pet, Dog and Cat entity classes. Dog and Cat classes extend Pet.
Also I have PetDTO, DogDTO and CatDTO annotated with #JsonSubtype so Jackson resolves well the class of the dtos.
I want to write a mapper using MapStruct that takes a PetDTO entity (can be a DogDTO or a CatDTO) and returns a Dog or a Cat.
For me in this case, the main goal of using a mapping library is to avoid awful code using instanceof.
Any idea? Thanks!
Not currently possible out-of-the-box - see this ticket in mapstruct's GitHub: #366 Support for abstract class mapping or classes with base class. You can try to push it there or maybe contribute this feature yourself. Looks like a reasonable feature to ask for.
I guess that with the current state of affairs this is your best option:
#Mapper
public interface PetMapper {
default PetDTO toPetDto(Pet pet) {
if (pet instanceof Dog) {
return toDogDTO((Dog) pet);
}
if (pet instanceof Cat) {
return toCatDTO((Cat) pet);
}
throw new IllegalArgumentException("Unknown subtype of Pet");
}
default Pet toPetEntity(PetDTO petDTO) {
if (petDTO instanceof DogDTO) {
return toDogEntity((DogDTO) petDTO);
}
if (petDTO instanceof CatDTO) {
return toCatEntity((CatDTO) petDTO);
}
throw new IllegalArgumentException("Unknown subtype of PetDTO");
}
DogDTO toDogDTO(Dog dog);
Dog toDogEntity(DogDTO dogDTO);
CatDTO toCatDTO(Cat cat);
Cat toCatEntity(CatDTO catDTO);
}
The way I ended up implementing a Mapper for a similar case as above was using a combination of a switch-type, with MapStruct Update Existing and creation Mappers.
In my case a property on the source object dictated the subclass we had to generate.
I initially had different mappers for each subtype, but the duplication of the common mapped properties just seemed wrong. So I came up with the following, leveraging the ability of MapStruct to use updating mappers in order to tackle the common parent type properties:
import org.mapstruct.*;
#Mapper
#Named("QualifierPetMapper")
public interface PetMapper {
#Named("DelegatingPetMapper")
#BeanMapping(ignoreByDefault = true)
default PetTarget mapPet(PetSource petSource) {
switch (petSource.getPetType()) {
case "DOG":
DogTarget dogTarget = mapDog(petSource);
updatePet(dogTarget, petSource);
return (dogTarget);
case "CAT":
CatTarget catTarget = mapCat(petSource);
updatePet(catTarget, petSource);
return (catTarget);
default:
throw new CustomException("Unsupported Pet type: "+ petSource.getPetType());
}
}
#BeanMapping(ignoreByDefault = true)
// Specific mappings for Dog
#Mapping(target = "dogfood.name", source = "dogfoodName")
DogTarget mapDog(PetSource petSource);
#BeanMapping(ignoreByDefault = true)
// Specific mappings for Cat
#Mapping(target = "fish.name", source = "favoriteFish")
CatTarget mapCat(PetSource petSource);
#Named("RootPetMapper")
#BeanMapping(ignoreByDefault = true)
// Common properties for Pet
#Mapping(target = "weight.value", source = "weightValue")
#Mapping(target = "name.value", source = "petName")
#Mapping(target = "color", source = "mainColor")
void updatePet(#MappingTarget PetTarget petTarget, PetSource petSource);
}

Assign Mapped Object to Expression Result in LINQ to Entities

I have the following child object that we use an expression to map our 'entity' to our 'domain' model. We use this when specifically calling our ChildRecordService method GetChild or GetChildren:
public static Expression<Func<global::Database.Models.ChildRecord, ChildRecord>> MapChildRecordToCommon = entity => new ChildRecord
{
DateTime = entity.DateTime,
Type = entity.Type,
};
public static async Task<List<ChildRecord>> ToCommonListAsync(this IQueryable<global::Database.Models.ChildRecord> childRecords)
{
var items = await
childRecords.Select(MapChildRecordToCommon).ToListAsync().EscapeContext();
return items;
}
public async Task<List<ChildRecord>> GetChildRecords()
{
using (var uow = this.UnitOfWorkFactory.CreateReadOnly())
{
var childRecords= await uow.GetRepository<IChildRecordRepository>().GetChildRecords().ToCommonListAsync().EscapeContext();
return childRecords;
}
}
So that all works just fine. However we have another object that is a parent to that child, that in SOME cases, we also wish to get the child during the materialisation and mapping process.
In other words the standard object looks as such:
private static Expression<Func<global::Database.Models.Plot, Plot>> MapPlotToCommonBasic = (entity) => new Plot
{
Id = entity.Id,
Direction = entity.Direction,
Utc = entity.Utc,
Velocity = entity.Velocity,
};
However what I also want to map is the Plot.ChildRecord property, using the expression MapChildRecordToCommon I have already created. I made a second expression just to test this:
private static Expression<Func<global::Database.Models.Plot, Plot>> MapPlotToCommonAdvanced = (entity) => new Plot
{
ChildRecord = MapChildRecordToCommon.Compile() (entity.ChildRecord)
};
This fails:
System.NotSupportedException
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
Is there a way to reuse my existing expression for ChildRecord, to materialise the object of ChildRecord (ie. one to one/singular not multiple) on the Plot object? I think my trouble is caused by there being just one object and being unable to use the .Select(Map) method. I am not too great at expressions and have hit a wall with this.
For reference, there are actually up to 5 or 6 other child objects on the "Plot" object that I also want to make expressions for.
I resolved this by using the third party library LinqKit.
The library allowed the use of 2 methods, .AsExpandable() (which allows for the expressions to properly compile and be invoked as I understand), and .Invoke() as an extension method to an expression, rather than calling Expression.Invoke(yourexpression). I included a null check just in case.
My code now looks as follows:
public static async Task<List<Plot>> ToCommonListAsync(this IQueryable<global::Database.Models.Plot> plots)
{
var items = await
plots.AsExpandable().Select(MapPlotToCommon).ToListAsync().EscapeContext();
return items;
}
private static Expression<Func<global::Database.Models.Plot, Plot>> MapPlotToCommon = (entity) => new Plot
{
Id = entity.Id,
Direction = entity.Direction,
Utc = entity.Utc,
Velocity = entity.Velocity,
ChildRecord = entity.ChildRecord != null ? MapChildRecordToCommon.Invoke(entity.ChildRecord) : default
};
public static Expression<Func<global::Database.Models.ChildRecord, ChildRecord>> MapChildRecordToCommon = entity => new ChildRecord
{
DateTime = entity.DateTime,
Type = entity.Type,
};

MapStruct: How to pass input object to expression?

In MapStruct version 1.1.0.Final, this was possible....
#Mappings({
#Mapping(target = "transaction.process.details", expression = "java(MappingHelper.mapDetails(request))"),
//more mappings
})
Response requestToResponse(Request request);
It was possible, since the mapDetails method was (by coincidence?) generated into the requestToResponse method. That's why request was not null.
Now, since 1.1.0.Final didn't work with Lombok, I had to upgrade to 1.2.0.CR2. With this version, the mapDetails will be generated into a separate method where request is not passed, so request is null within this method now and I get a NPE with the expression. (It's a sub-sub-method of requestToResponse now.)
Did I misuse the expression, so did it just work by coincidence, or does the new version has a bug? If no bug, how do I have to pass the request instance to the expression properly?
You were / are misusing the expression. What you need to do is to map your target to your source parameter.
#Mapper(uses = { MappingHelper.class })
public interface MyMapper {
#Mappings({
#Mapping(target = "transaction.process.details", source = "request"),
//more mappings
})
Response requestToResponse(Request request);
}
MapStruct then should create intermediary methods and use the MappingHelper and invoke the mapDetails method. In case you have multiple methods that map from Request to whatever type details are then you are going to need to used qualified mappings (see more here in the documentation).
It will look something like:
public class MappingHelper {
#Named("mapDetails") // or the better type safe one with the meta annotation #Qualifier
public static String mapDetails(Request request);
}
And your mapping will look like:
#Mapper(uses = { MappingHelper.class })
public interface MyMapper {
#Mappings({
#Mapping(target = "transaction.process.details", source = "request", qualifiedByName = "mapDetails"), //or better with the meta annotation #Qualifier qualifiedBy
//more mappings
})
Response requestToResponse(Request request);
}

MyBatis - ResultMap according to javaType

Hello StackOverflowers,
There is something I don't get about MyBatis resultMap.
The model I'm working on is beeing updated. We decided to create a new graph of objects which reflects our future DB schema (the current one is awful).
To sum up our problem, here is a simple case:
The current Object whith is related to table SITE is org.example.model.SiteModel. We created a new Object called org.example.entity.Site. (The package name is temporary).
The goal is now to use the existing SQL request developed thank to MyBatis and add a new ResultMap linked to the return type of our method.
Here is a an example:
/**
* Get all site defined as template.
*/
#Select("SELECT * FROM SITE WHERE ISTEMPLATE = 'True'")
#ResultMap({"siteResMap" , "siteResultMap"})
#Options(statementType = StatementType.CALLABLE)
<T> List<T> findTemplates();
Then, in an XML configuration file, we defined the following mappings:
...
<resultMap id="siteResMap" type="org.example.entity.Site" />
<resultMap id="siteResultMap" type="org.example.model.SiteModel" />
...
And then we call the method from our DAO:
List<Site> site = siteDao.findTemplates();
List<SiteModel> siteMod = siteDao.findTemplates();
What we are expecting from this is a dynamic interpretation from MyBatis, taking the right ResultMap according to the computed return type.
But both list are shown as List<org.example.entity.Site> from debuger.
It makes me think that the first ResultMap is taken, ignoring the second one.
Am I missing something ? Is there a way to make MyBatis behave in such way ?
Regards
After a lot a research and code exploration, we found out that the String[] of ResultMap is not designed to link java return types to the resultMap.
This is function retrieving the resultmap (from org.apache.ibatis.executor.resultset.DefaultResultSetHandler)
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResulSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
It explains why we always got a List of elements of type of the first resultMap.
We created a new Dao to map new object types.