mapstruct - Propagate parent field value to collection of nested objects - java

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.

Related

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);
}

Nested Mapping using MapStruct

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());
}
}

Java JPA Preventing Proxies from calling db

I have a spring boot (1.5.4.RELEASE) project using Java 8. I have an entity and it's related domain class like this:
#Entity
#Table(name = "Foo", schema = "dbo")
public class FooEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private int id;
#Column(name="Name")
private String name;
#Column(name="Type")
private String type;
#Column(name="Color")
private String color;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "Car")
private Car car;
//getter and setter
}
public class Foo {
private int id;
private String name;
private String type;
private String color;
private Car car;
//Constructors and getters
}
I want to create a repository that fetches this Foo object from the DB but only fetching the complex fields if the user asks for them to prevent unnecessary join statements. The repo looks like this:
import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;
#Repository
public class FooRepository {
private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);
public FooRepository getFooByName(String name) {
query.where(fooEntity.name.eq(name));
return this;
}
public FooRepository withCar() {
query.leftJoin(fooEntity.car, carEntity).fetchJoin();
return this;
}
public Foo fetch() {
FooEntity entity = query.fetchOne();
return FooMapper.mapEntityToDomain().apply(entity);
}
}
So a barebones call for a Foo object will return the Entity with values for all the fields except for the car field. If the user wants car information then they have to explicitly call withCar.
Here is the mapper:
public class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain() {
return entity -> {
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), e.getCar());
};
}
}
The problem is when you do e.getCar() if the value is not there (i.e. there's a proxy present) JPA will go out and fetch it for you. I don't want this to be the case. It will just grab the values and map them to the domain equivalent if it's not there then null.
One solution that I've heard (and tried) is calling em.detach(entity); however, this doesn't work as I intended because it throws an exception when you try to access getCar and I've also heard this is not best practice.
So my question is what is the best way to create a repo using a builder pattern on a JPA entity and not have it call the DB when trying to map.
You could create a utility method that will return null if the given object is a proxy and is not initialized:
public static <T> T nullIfNotInitialized(T entity) {
return Hibernate.isInitialized(entity) ? entity : null;
}
Then you can call the method wherever you need it:
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), nullIfNotInitialized(e.getCar()));
Just map it to a new object and leave out the Car relation, this is the standard approach. You can use MapStruct and just ignore the car field during mapping: http://mapstruct.org/documentation/stable/reference/html/#inverse-mappings
Just don't map the car... Map a field holding the ID and use another method to get the actual Car. I would use a distinctive method name, to differentiate it from the other getters.
class FooEntity {
#Column
private int carId;
public int getCarId() {
return carId;
}
public void setCarId(int id) {
this.carId = id;
}
public Car fetchCar(CarRepository repo) {
return repo.findById(carId);
}
}
You can write query on top of JPA
#Query("select u from Car c")
import org.springframework.data.repository.CrudRepository;
import com.example.model.FluentEntity;
public interface DatabaseEntityRepository extends CrudRepository<FooEntity , int > {
}
As you said
I don't want this to be the case. It will just grab the values and map them to the domain equivalent, if it's not there then null.
Then you just set it to null, because the field car will always not be there.
Otherwise, if you mean not there is that the car not exists in db, for sure a subquery(call the proxy) should be made.
If you want to grab the car when call Foo.getCar().
class Car {
}
class FooEntity {
private Car car;//when call getCar() it will call the proxy.
public Car getCar() {
return car;
}
}
class Foo {
private java.util.function.Supplier<Car> carSupplier;
public void setCar(java.util.function.Supplier<Car> carSupplier) {
this.carSupplier = carSupplier;
}
public Car getCar() {
return carSupplier.get();
}
}
class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain() {
return (FooEntity e) -> {
Foo foo = new Foo();
foo.setCar(e::getCar);
return foo;
};
}
}
Make sure you have the db session ,when you call Foo.getCar()
You could try adding state to your repository and influence the mapper. Something like this:
import static com.test.entities.QFooEntity.fooEntity;
import static com.test.entities.QCarEntity.carEntity;
#Repository
public class FooRepository {
private final JPAQuery<FooEntity> query = createQuery().from(fooEntity);
private boolean withCar = false;
public FooRepository getFooByName(String name) {
query.where(fooEntity.name.eq(name));
return this;
}
public FooRepository withCar() {
query.leftJoin(fooEntity.car, carEntity).fetchJoin();
withCar = true;
return this;
}
public Foo fetch() {
FooEntity entity = query.fetchOne();
return FooMapper.mapEntityToDomain(withCar).apply(entity);
}
}
In your mapper, you then include a switch to enable or disable car lookups:
public class FooMapper {
public static Function<FooEntity, Foo> mapEntityToDomain(boolean withCar) {
return e -> {
return new Foo(e.getId(), e.getName(), e.getType(), e.getColor(), withCar ? e.getCar() : null);
};
}
}
If you then use new FooRepository().getFooByName("example").fetch() without the withCar() call, e.getCar() should not be evaluated inside FooMapper
You may want to use the PersistentUnitUtil class to query if an attribute of entity object is already loaded or not. Based on that you may skip the call to corresponding getter as shown below. JpaContext you need to supply to user entity bean mapper.
public class FooMapper {
public Function<FooEntity, Foo> mapEntityToDomain(JpaContext context) {
PersistenceUnitUtil putil = obtainPersistentUtilFor(context, FooEntity.class);
return e -> {
return new Foo(
e.getId(),
e.getName(),
e.getType(),
e.getColor(),
putil.isLoaded(e, "car") ? e.getCar() : null);
};
}
private PersistenceUnitUtil obtainPersistentUtilFor(JpaContext context, Class<?> entity) {
return context.getEntityManagerByManagedType(entity)
.getEntityManagerFactory()
.getPersistenceUnitUtil();
}
}

Spring class RequestBody and MongoRepository

I have class:
class TestClass {
#Id
private ObjectId id;
private ObjectId parentId;
private String name;
private String describe;
private String privateData;
public TestClass(ObjectId parentId, String name, String describe, String privateData) {
this.parrentId = parrentId;
this.name = name;
this.describe = describe;
this.privateDate = privateData;
}
// get/set methods...
}
Can I use this class in MongoRepository and #RequestBody? Is it safe? parrentId and privateData is private properties and RequestBody does not have to fill them.
mongorepository:
public interface TestClassRepository extends MongoRepository<TestClass, String> {
public TestClass findById(ObjectId id);
}
post method:
#RequestMapping(value="/testclass", method=RequestMethod.POST)
public void create(#RequestBody TestClass testClass) {
testClass.setParentId(...);
repo.insert(testClass);
}
For example:
{"name": "test", "describe": "test", "id": "54d5261a8314fe3c650d5b1d", "parentId": "54d5261a8314fe3c650d5b1d", "privateData": "WrongPrivateData"}
How can I do that it was impossible to set properties id, parentId, privateDate?
Or need I create new class for RequestBody? I don't want duplicate code.
It should be better and safe to use separate models for DAO and VO layers(view). If your models currently looks the same, it doesn't mean that they will stay the same in future. You can use the Dozer Mapping framework for mappings between your models. It's easy,fast and safe.
If you need to skip some field from mongotemplate mapping use #Transient annotation.
P.S. You don't need findById method, because mongotemplate already have find method which uses key as param. TestClass should have an empty constructor.

Generics in POJO - Is this a good practice

I have a Base Class.
#Data
class BaseDocument{
String id;
String name;
//Other fields
}
Say I have many classes that extends BaseDocument one below.
class NoteDocument extends BaseDocument{
String description;
Long lastModifiedDate;
//etc
}
It does not make sense to me to send entire document to UI in some cases. Most of the cases I need only id and name.
So for every document I have a VO class.
#Data
class BaseVO {
private String id;
private String name;
}
#Data
class NoteVO extends BaseVO{
//Nothing here now
}
And in NoteDocument I have.
public NoteVO getVo(){
Assert.notNull(getId());
NoteVO noteVo = new NoteVO();
noteVo.setName(getName());
noteVo.setId(getId());
return noteVo;
}
Now I have to copy this method in all the classes that extends BaseDocument.
Instead, I changed my BaseDocument like below.
#Data
class BaseDocument<V extends BaseVO>{
String id;
String name;
public V getVo(Class className) {
Assert.notNull(getId());
V vo = null;
try {
vo = (V) className.newInstance();
vo.setName(getName());
vo.setId(getId());
} catch (IllegalAccessException|InstantiationException e){
e.printStackTrace();
}
Assert.notNull(vo);
return vo;
}
}
I am new to generics. My first question, is this a good practice. Are there any problems in using reflection to create instance, any performance issues? Is there any better way to do achieve (write less code) this.
Edit: Suppose I need to display note in UI, Along with note I need to display name of the User who created note. I am using mongodb, when I save the note I also save UserVO in note, which will have user id and name of the user. If I save only user id while saving the note, I will have to do one more query to get the name of user while displaying. I want to avoid this.
Do not use reflection; use inheritance and maybe covariant return types instead. It will be faster, clearer, more precise, and easier to maintain. You may also find it useful to add methods to populate your VOs incrementally. I didn't come up with a clean way to apply generics to this situation, but I don't think you need them:
class BaseVO {
String id;
String name;
void setId(String id) {
this.id = id;
}
void setName(String name) {
this.name = name;
}
}
class NoteVO extends BaseVO {
// ...
}
#Data
class BaseDocument {
String id;
String name;
//Other fields
protected void populateBaseVO(BaseVO vo) {
vo.setId(id);
vo.setName(name);
}
public BaseVO getVO() {
BaseVO vo = new BaseVO();
populateBaseVO(vo);
return vo;
}
}
#Data
class NoteDocument extends BaseDocument {
String description;
Long lastModifiedDate;
// ....
protected void populateNoteVO(NoteVO vo) {
populateBaseVO(vo);
// ...
}
public NoteVO getVO() {
NoteVO vo = new NoteVO();
populateNoteVO(vo);
return vo;
}
}

Categories

Resources