Nested Mapping using MapStruct - java

class Identifier {
private long id;
private String type;
private List<Status> statuses;
}
class Customer {
private Identifier identifier;
}
class CustomerProfile {
private Customer customer;
}
class CustomerIdentifierDO {
private long id;
}
class CustomeDO {
private CustomerIdentiferDO custID;
}
class CustomerProfileDO {
private String category;
private List<Status> custStatuses;
private CustomeDO customer;
}
#Mapper
public interface CustomerProfileMapper {
CustomerProfile toCustomerProfile(CustomerProfileDO profileDO) ;
Customer toCustomer(CustomerDO customerDO);
Identifier toIdentifier(CustomerIdentifierDO identifierDO);
}
Everything works fine till this. Now I want to map custStatuses, category of CustomerProfileDO class to statuses and type of Identifier class. I've no idea how to supply CustomerProfileDO object to toIdentifier mapping method, so that I can include the mapping there itself. I tried following
#Mappings({
#Mapping(target = "customer.identifier.type", source = "category")
})
CustomerProfile toCustomerProfile(CustomerProfileDO profileDO) ;
But this nested mapping is overriding all the mapping config of below method. That should not happen.
toIdentifer(CustomerIdentifierDO identifierDO)
Is there any way to achieve this?

Currently MapStruct can pass source parameters to single methods. In order to achieve what you are looking for (without using nested target types you would need to use something like #AfterMapping. It can look like:
#Mapper
public interface CustomerProfileMapper {
CustomerProfile toCustomerProfile(CustomerProfileDO profileDO) ;
Customer toCustomer(CustomerDO customerDO);
Identifier toIdentifier(CustomerIdentifierDO identifierDO);
#AfterMapping
default void afterMapping(#MappingTarget CustomerProfile profile, CustomerProfieDO profileDO) {
Identifier identifier = profile.getCustomer().getIdentifier();
identifier.setStatus(profileDO.setStatus());
identifier.setType(profileDO.setCategory());
}
}

Related

JPA #Convert annotation on a method

The JPA #Convert annotation says it's applicable to a method (as well as field and type).
What is an example of a situation where it is useful?
#Convert allows us to map JDBC types to Java classes.
Let's consider the code block below:-
public class UserName implements Serializable {
private String name;
private String surname;
// getters and setters
}
#Entity(name = "UserTable")
public class User {
private UserName userName;
//...
}
Now we need to create a converter that transforms the PersonName attribute to a database column and vice-versa.
Now we can annotate our converter class with #Converter and implement the AttributeConverter interface. Parametrize the interface with the types of the class and the database column, in that order:
#Converter
public class UserNameConverter implements
AttributeConverter<UserName, String> {
private static final String SEPARATOR = ", ";
#Override
public String convertToDatabaseColumn(UserName userName) {
if (userName == null) {
return null;
}
....
}
}
In order to use the converter, we need to add the #Convert annotation to the attribute and specify the converter class we want to use:
#Entity(name = "PersonTable")
public class User {
#Convert(converter = UserNameConverter.class)
private UserName userName;
// ...
}
For more details you can refer below:- jpa-convert

MapStruct mapping on objects of type List

I am trying to use MapStruct for a structure similar to the following:
#Data
public class ClassAEntity {
private int id;
private String name;
private String numT;
private List<ClassBEntity) bs;
}
#Data
public class ClassBEntity {
private int id;
private String name;
private String numT;
private List<Other> oc;
}
#Data
public class ClassA {
private int id;
private String name;
private List<ClassB) bs;
}
#Data
public class ClassB {
private int id;
private String name;
private List<Other> oc;
}
In the interface I have added the following mapping:
ClassAEntity map(ClassA classA, String numT)
I get a warning because it can't map numT to classBEntity.numT and I can't add it with #Mapping in the following way:
#Mapping(source = "numT", target = "bs[].numT")
On the other hand I need to ignore the parameter oc of classBEntity because "Other" object contains classAEntity and forms a cyclic object. (because I use oneToMany JPA). I have tried the following:
#Mapping(target = "bs[].oc", ignore = true)
Thank you for your help
MapStruct does not support defining nested mappings for collections. You will have to define more explicit methods.
For example to map numT into bs[].numT and ignore bs[].oc you'll need to do something like:
#Mapper
public MyMapper {
default ClassAEntity map(ClassA classA, String numT) {
return map(classA, numT, numT);
}
ClassAEntity map(ClassA classA, String numT, #Context String numT);
#AfterMapping
default void setNumTOnClassBEntity(#MappingTarget ClassBEntity classB, #Context String numT) {
classB.setNumT(numT);
}
#Mapping(target = "oc", ignore = "true")
ClassBEntity map(ClassB classB);
}

Basic attribute should not be - with my own type

I have a problem with the hibernate entity, and I would like to know if it is something I overlooked or if it is a bug in IntelliJ IDEA.
I have a Value object bank account:
class BankAccount
{
private String value;
public BankAccount(String value) {
// validation
this.value;
}
}
Which has defined it's own hibernate type:
public class BankAccountType extends AbstractSingleColumnStandardBasicType<BankAccount> {
public static final BankAccountType INSTANCE = new BankAccountType();
public static final String NAME = "bankAccount";
public BankAccountType() {
super(LongVarcharTypeDescriptor.INSTANCE, BankAccountTypeDescriptor.INSTANCE);
}
#Override
public String getName() {
return null;
}
}
And I have an entity:
#Entity
#TypeDefs({
#TypeDef(
name = BankAccountType.NAME,
typeClass = BankAccountType.class,
defaultForType = BankAccount.class
)
})
class User {
private UUID id;
//...
#Column
private BankAccount bankAccount;
//...
}
It works perfectly, but IDEA keeps telling me 'Basic attribute should not be BankAccount.'
Is there any way, how to get rid of this error without changing my entities? Is it a good idea to use value objects as a column in my entities?
Thanks a lot!

mapstruct - Propagate parent field value to collection of nested objects

Is it possible to propagate value form a parent object to collection of nested objects? For example
Source DTO classes
class CarDTO {
private String name;
private long userId;
private Set<WheelDto> wheels;
};
class WheelDto {
private String name;
}
Target entity classes
class Car {
private String name;
private long userId;
private Set<Wheel> wheels;
};
class Wheel {
private String name;
private long lastUserId;
}
As you could see I do not have lastUserId on WheelDto, hence I would like to map userId from CarDto to lastUserId on WheelDto on each object in a wheels collection
I tried
#Mapping(target = "wheels.lastUserId", source = "userId")
but no luck
Currently it is not possible to pass a property. However, you could solve this via #AfterMapping and / or #Context.
Update Wheel after Car mapping
This would though mean that you would need to iterate twice over the Wheel. It can look like
#Mapper
public interface CarMapper {
Car map(CarDto carDto);
#AfterMapping
default void afterCarMapping(#MappingTarget Car car, CarDto carDto) {
car.getWheels().forEach(wheel -> wheel.setLastUserId(carDto.getUserId()));
}
}
Pass #Context while mapping wheel to have state during the mapping
If you want to iterate only once through the Wheel then you can pass a #Context object that would get the userId from the CarDto before mapping the car and then set it on the Wheel in an after mapping. This mapper can look like:
#Mapper
public interface CarMapper {
Car map(CarDto carDto, #Context CarContext context);
Wheel map(WheelDto wheelDto, #Context CarContext context);
}
public class CarContext {
private String lastUserId;
#BeforeMapping
public void beforeCarMapping(CarDto carDto) {
this.lastUserId = carDto.getUserId();
}
#AfterMapping
public void afterWheelMapping(#MappingTarget Wheel wheel) {
wheel.setLastUserId(lastUserId);
}
}
The CarContext would be passed to the wheel mapping method.

Return List type in JPA

I have DTO structure like :
public class ADto{
private String name;
private String id;
private List<BDto> bdtos;
//Created constructor using fields
}
public class BDto{
private String id;
private String code;
private List<CDto> cdtos;
//Created constructor using fields
}
public class CDto{
private String mKey;
private String mVal;
//Created constructor using fields
}
Used Spring MVC for fetching the data.
Below query is working perfectly fine and binding the data :
#org.springframework.data.jpa.repository.Query("select new pkg.ADto(id,name) from AEntity a where a.id=?1")
public ADto getAData(Long id);
How can I fetch the data for the list which is in turn composed of further list using the above method?
If you want to return DTOs instead on enitites, you need to provide mapping between DTOs and entities. With JPQL query, the only option is to provide that mapping in constructor of the resulting object. Therefore, you need to add a constructor to ADto, which accepts BEntities, and map all nested entities to dtos in that constructor. Or in more object oriented way, the new constructor will accept AEntity as the only argument.
This is how it could look like:
getAData() method in the repository (JPQL is slightly modified by adding a.bEntities to result):
#org.springframework.data.jpa.repository.Query("select new pkg.ADto(id,name, a.bEntities) from AEntity a where a.id=?1")
public ADto getAData(Long id);
New constructor in ADto:
public class ADto{
private String name;
private String id;
private List<BDto> bdtos;
public ADto(String id, String name, List<BEntity> bEntities) {
this.id = id; this.name = name;
this.bdtos = new ArrayList<>();
for (BEntity b : bEntities) {
BDto bdto = new BDto(b.id, b.code, b.cEntities);
/* you need to pass cEntities and map them again in the BDto
* constructor, or you may do the apping in ADto constructor
* and only pass mapped values to BDto constructor */
}
}
}
You have to enable eager fetch:
#OneToMany(mappedBy = "adto", fetch = FetchType.EAGER)
private List<BDto> bdtos;
Then you can fetch it like this i.e.:
ADto findById(Long id); // literally!

Categories

Resources