I am trying to apply MapStruct to DDD. I created my entities with ad hoc setters like this:
#AllArgsContructor
#NoArgsConstructor //necessary to MapStruct
#Getter
class Employee {
private String id;
private String name;
public void updateName(String newName) {
this.name = newName;
}
}
and I have a EmployeeDto to send data to frontend API:
#Getter
#AlArgsConstructor
#NoArgsConstructor //necessary to MapStruct
class EmployeeDto {
private String name;
private String id;
}
so, I am trying to use MapStruct to map, but it is not working because my ad hoc setter. How could I resolve this problem?
You have to implement custom AccessorNamingStrategy. The way how to do it is well documented in section 13.1. Custom Accessor Naming Strategy of a MapStruct documentation.
In your case it should look something like this:
public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy {
#Override
public boolean isSetterMethod(ExecutableElement method) {
String methodName = method.getSimpleName().toString();
return methodName.startsWith("update") && methodName.length() > 6;
}
#Override
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
if (isSetterMethod(getterOrSetterMethod)) {
String methodName = getterOrSetterMethod.getSimpleName().toString();
return IntrospectorUtils.decapitalize(methodName.substring(6));
} else {
return super.getPropertyName(getterOrSetterMethod);
}
}
}
I have trying to implement MapStruct mapping library. I have made samples and for simple mapping it works fine but I stucked in 1 issue.
I have 2 jpa entity classes which have two way relationships. One is in another and another is in one. It creates cyclic mapping issue so MapStruct throws StackOverflow error.
I have created minimal code to reproduce the case on github.
Sample code:
public class A {
private Long id;
private String name;
private B bData;
//getter-setter
}
public class B {
private Long id;
private String name;
private Set<A> aData;
//getter-setter
}
DataGenerator
public class DataGenerator {
public static A generateData(){
A a = new A();
a.setId(1L);
a.setName("foo");
B b = new B();
b.setId(2L);
b.setName("bar");
A a2 = new A();
a2.setId(3L);
a2.setName("john");
a2.setbData(b);
A a3 = new A();
a3.setId(4L);
a3.setName("doe");
a3.setbData(b);
Set<A> aData = new HashSet<A>();
aData.add(a2);
aData.add(a3);
b.setaData(aData);
a.setbData(b);
return a;
}
}
Mapper
#Mapper
public interface CustomMapper {
CustomMapper INSTANCE = Mappers.getMapper(CustomMapper.class);
ADto atoADto(A a);
}
App
public class AppMain {
public static void main(String[] args) {
A a = DataGenerator.generateData();
ADto aDto = CustomMapper.INSTANCE.atoADto(a);
System.out.println(aDto.getId());
}
}
Dto/Destination classes are same as original source classes.
The main is cyclic/recursive mapping issue which causes stackoverflow error.
Same thing working with spring BeanUtils.copyProperties but I want to implement MapStruct. Currently I am thinking to replace spring BeanUtils with MapStruct.
any suggestions?
See this mapstruct github issue for the solution, which is to ignore the field causing the recursion. I quote:
"You can achieve it with the #Qualifier. You can use #Named and qualifiedByName, or you can use your own custom #CountryWithoutCities qualifier with qualifiedBy.
Class country{
String id;
String name;
List<City> cities;
}
Class City{
String id;
String name;
Country country;
}
#Mapper(uses = CityMapper.class)
interface CountryMapper {
#Mapping( target = "cities", qualifiedByName = "noCountry")
CountryDto toDto(Country country);
#CountryWithoutCities
#Mapping( target = "cities", ignore = true)
CountryDto toDtoWithoutCities(Country country);
}
#Mapper(uses = CountryMapper.class)
interface CityMapper {
#Named( "noCountry" )
#Mapping( target = "country", ignore = true)
CityDto toDtoWithoutCountry(City city);
#Mapping( target = "country", qualifiedBy= CountryWithoutCities.class)
CityDto toDto(City city);
}
There's an example here in the MapStruct repo how to deal with cycles and recursion. Basically you need to keep track of state. The example makes use of a context object to do so.
I use MongoDBRepository in spring boot, and when I save some object in database everything is ok. but when I find object by id spring does not allow do that.
I try to change VehicleRoutingProblemSolution type to Object type, but VehicleRoutingProblemSolution have other object field PickupService and it without default constructor to. And yes, this class has immutable... I can't create default constructors, what can I do?
import com.fasterxml.jackson.annotation.JsonProperty;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = "vrp_solutions")
public class VrpSolutionHolder {
// Specifies the solution id
#Id
#JsonProperty("id")
private String id;
// Specifies the solution id
#JsonProperty("solution")
private VehicleRoutingProblemSolution vehicleRoutingProblemSolution;
// Created at timestamp in millis
#JsonProperty("created_at")
private Long created_at = System.currentTimeMillis();
public VrpSolutionHolder(String id, VehicleRoutingProblemSolution vehicleRoutingProblemSolution) {
this.id = id;
this.vehicleRoutingProblemSolution = vehicleRoutingProblemSolution;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public VehicleRoutingProblemSolution getVehicleRoutingProblemSolution() {
return vehicleRoutingProblemSolution;
}
public void setVehicleRoutingProblemSolution(VehicleRoutingProblemSolution vehicleRoutingProblemSolution) {
this.vehicleRoutingProblemSolution = vehicleRoutingProblemSolution;
}
public Long getCreated_at() {
return created_at;
}
public void setCreated_at(Long created_at) {
this.created_at = created_at;
}
}
org.springframework.web.util.NestedServletException: Request
processing failed; nested exception is
org.springframework.data.mapping.model.MappingInstantiationException:
Failed to instantiate
com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution
using constructor NO_CONSTRUCTOR with arguments
I ran into the exact same problem. A persistent immutable class containing other class instances, throwing that aforementioned exception when retrieved by this repository method:
public interface ProjectCodeCacheRepository extends MongoRepository<CachedCode, String> {
public CachedCode findByCode(String code);
public List<CachedCode> findByClientId(UUID clientId);
}
...
List<CachedCode> cachedForClient = this.codeCacheRepo.`**findByClientId**`(clientId);
...
Following Erwin Smouts hints, this is nicely fixed by giving it a special constructor annotated org.springframework.data.annotation.PersistenceConstructor like so:
#Document(collection="cachedcodes")
public class CachedCode {
#PersistenceConstructor
public CachedCode(String code, UUID clientId, LocalDateTime expiration) {
this.code = code;
this.clientId = clientId;
this.expiration = expiration;
}
public CachedCode(String code, UUID clientId, long secondsExpiring) {
this.code = code;
this.clientId = clientId;
this.expiration = LocalDateTime.now().plusSeconds(secondsExpiring);
}
public UUID getClientId( ) {
return this.clientId;
}
public String getCode() {
return this.code;
}
public boolean hasExpired(LocalDateTime now) {
return (expiration.isBefore(now));
}
...
#Id
private final String code;
private final UUID clientId;
private final LocalDateTime expiration;
}
So, you should check if your VehicleRoutingProblemSolution has a) a constructor that matches the database fields (check in mongo client) and b) is annotated to be the one used by the driver (or whichever piece of Spring magic under the hood).
If your framework tool requires (visible) no-arg constructors (plus accompanying setters), and the class you have is required to stay as is, then you could roll your own, say, MutableVehicleRoutingProblemSolution where in the setters you could have :
this.vehicleRoutingProblemSolution = new VehicleRoutingProblemSolution(vehicleRoutingProblemSolution.getId(), newSolution);
Thus your MutableVehicleRoutingProblemSolution wraps around the existing VehicleRoutingProblemSolution.
Hacky smell to it, but it fits the requirements.
(Or you could try to find a tool that is able to use, not annotations on the contained fields, but annotations on constructor arguments.)
This is a problem where the corresponding class does not have a no-arg constructor like - I was facing an issue with java.io.File.
Solution:
In general - change the declaration to Object class and convert where we are using the class.
from
class MyClass{
File myfile;
}
to
class MyClass{
Object myFile;
}
For anyone using lombok, you need to remove the #Builder annotation on your class and use #Data instead, or follow the above solution to provide a specialized constructor
Oddly, I received this when I attempted to decorate a custom interface with ...
#Document(collection = "Person")
Example:
package test.barry.interfaces;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.UpdateDefinition;
#Document(collection = "Person")
public interface CustomRepository
{
void updatex(Query filterPredicate, UpdateDefinition updatePredicate);
}
Given the Source class as defined below:
class Source{
private String name;
private int age;
private List<Phone> phones;
// getters and setters
}
and the Phone class as defined below:
class Phone{
private Long id;
private String phoneNumber;
// getters and setters
}
and the Target class as defined below:
class Target{
private String name;
private int age;
private List<Long> idsPhones;
// getters and setters
}
I have an interface is:
#Mapper
interface MyMapper{
Target toTarget(Source source);
Source toSource(Target target);
}
How can I map the List of Phones from the Source class to a List of idsPhones in the Target Class and vice versa?
In order to achieve this you need to help MapStruct a bit by telling how to map from Phone into Long. And the reverse as well.
Your mapper needs to look something like:
#Mapper(uses = PhoneRepository.class)
interface MyMapper {
#Mapping(target = "idsPhones", source = "phones")
Target toTarget(Source source);
#InheritInverseMapping
Source toSource(Target target);
default Long fromPhone(Phone phone) {
return phone.getId();
}
}
If your PhoneRepository contains a method that accepts a Long and returns Phone then MapStruct will automatically know what to do and invoke that method.
I have a class called as "XYZClientWrapper" , which have following structure:
#Builder
XYZClientWrapper{
String name;
String domain;
XYZClient client;
}
What I want no build function generated for property XYZClient client
Does Lombok supports such use case?
Yes, you can place #Builder on a constructor or static (factory) method, containing just the fields you want.
Disclosure: I am a Lombok developer.
Alternatively, I found out that marking a field as final, static or static final instructs #Builder to ignore this field.
#Builder
public class MyClass {
private String myField;
private final String excludeThisField = "bar";
}
Lombok 1.16.10
Create the builder in code and add a private setter for your property.
#Builder
XYZClientWrapper{
String name;
String domain;
XYZClient client;
public static class XYZClientWrapperBuilder {
private XYZClientWrapperBuilder client(XYZClient client) { return this; }
}
}
Here is my preferred solution. With that, you can create your field client at the end and have it depending on other fields that previously set by the builder.
XYZClientWrapper{
String name;
String domain;
XYZClient client;
#Builder
public XYZClientWrapper(String name, String domain) {
this.name = name;
this.domain = domain;
this.client = calculateClient();
}
}
For factory static method example
class Car {
private String name;
private String model;
private Engine engine; // we want to ignore setting this
#Builder
private static Car of(String name, String model){
Car car=new Car();
car.name = name;
car.model = model;
constructEngine(car); // some static private method to construct engine internally
return car;
}
private static void constructEngine(Car car) {
// car.engine = blabla...
// construct engine internally
}
}
then you can use as follows:
Car toyotaCorollaCar=Car.builder().name("Toyota").model("Corolla").build();
// You can see now that Car.builder().engine() is not available
Notice the static method of will be called whenever build() is called, so doing something like Car.builder().name("Toyota") won't actually set the value "Toyota" into name unless build() is called and then assigning logic within the constructor static method of is executed.
Also, Notice that the of method is privately accessed so that build method is the only method visible to the callers
I found that I was able to implement a "shell" of the static Builder class, add the method I want to hide with a private access modifier, and it is no longer accessible in the builder. Likewise I can add custom methods to the builder as well.
package com.something;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embedded;
import javax.persistence.Entity;
import java.time.ZonedDateTime;
#Data
#Entity
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class MyClass{
//The builder will generate a method for this property for us.
private String anotherProperty;
#Embedded
#AttributeOverrides({
#AttributeOverride(name = "localDateTime", column = #Column(name = "some_date_local_date_time")),
#AttributeOverride(name = "zoneId", column = #Column(name = "some__date_zone_id"))
})
#Getter(AccessLevel.PRIVATE)
#Setter(AccessLevel.PRIVATE)
private ZonedDateTimeEmbeddable someDateInternal;
public ZonedDateTime getSomeDate() {
return someDateInternal.toZonedDateTime();
}
public void setSomeDate(ZonedDateTime someDate) {
someDateInternal = new ZonedDateTimeEmbeddable(someDate);
}
public static class MyClassBuilder {
//Prevent direct access to the internal private field by pre-creating builder method with private access.
private MyClassBuilder shipmentDateInternal(ZonedDateTimeEmbeddable zonedDateTimeEmbeddable) {
return this;
}
//Add a builder method because we don't have a field for this Type
public MyClassBuilder someDate(ZonedDateTime someDate) {
someDateInternal = new ZonedDateTimeEmbeddable(someDate);
return this;
}
}
}
I found one more solution
You can wrap your field into initiated final wrapper or proxy.
The easiest way to wrap it into AtomicReference.
#Builder
public class Example {
private String field1;
private String field2;
private final AtomicReference<String> excluded = new AtomicReference<>(null);
}
You can interact with it inside by get and set methods but it won't be appeared in builder.
excluded.set("Some value");
excluded.get();
Adding a so called 'partial builder' to the class with Lombok #Builder can help. The trick is to add a inner partial builder class like this:
#Getter
#Builder
class Human {
private final String name;
private final String surname;
private final Gender gender;
private final String prefix; // Should be hidden, depends on gender
// Partial builder to manage dependent fields, and hidden fields
public static class HumanBuilder {
public HumanBuilder gender(final Gender gender) {
this.gender = gender;
if (Gender.MALE == gender) {
this.prefix = "Mr.";
} else if (Gender.FEMALE == gender) {
this.prefix = "Ms.";
} else {
this.prefix = "";
}
return this;
}
// This method hides the field from external set
private HumanBuilder prefix(final String prefix) {
return this;
}
}
}
PS: #Builder allows the generated builder class name to be changed. The example above assumed the default builder class name is used.
I have another approach using #Delegate and Inner Class, which supports "computed values" for the excluded fields.
First, we move the fields to be excluded into an Inner Class to avoid Lombok from including them in the Builder.
Then, we use #Delegate to expose Getters/Setters of the builder-excluded fields.
Example:
#Builder
#Getter #Setter #ToString
class Person {
private String name;
private int value;
/* ... More builder-included fields here */
#Getter #Setter #ToString
private class BuilderIgnored {
private String position; // Not included in the Builder, and remain `null` until p.setPosition(...)
private String nickname; // Lazy initialized as `name+value`, but we can use setter to set a new value
/* ... More ignored fields here! ... */
public String getNickname(){ // Computed value for `nickname`
if(nickname == null){
nickname = name+value;
}
return nickname;
}
/* ... More computed fields' getters here! ... */
}
#Delegate #Getter(AccessLevel.NONE) // Delegate Lombok Getters/Setters and custom Getters
private final BuilderIgnored ignored = new BuilderIgnored();
}
It will be transparent to outside of this Person class that position and nickname are actually inner class' fields.
Person p = Person.builder().name("Test").value(123).build();
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=null, nickname=Test123))
p.setNickname("Hello World");
p.setPosition("Manager");
System.out.println(p); // Person(name=Test, value=123, ignored=Person.BuilderIgnored(position=Manager, nickname=Hello World))
Pros:
Do not force the excluded fields to be final
Support computed values for the excluded fields
Allow computed fields to refer to any fields set by the builder (In other words, allow the inner class to be non-static class)
Do not need to repeat the list of all fields (Eg. listing all fields except the excluded ones in a constructor)
Do not override Lombok library's #Builder (Eg. creating MyBuilder extends FooBuilder)
Cons:
The excluded fields are actually fields of Inner Class; however, using private identifier with proper Getters/Setters you can mimic as if they were real fields
Therefore, this approach limits you to access the excluded fields using Getters/Setters
The computed values are lazy initialized when Getters are invoked, not when .build().
One method I like and use is this.
Keep required parameters in constructor, and set optional through builder. Works if number of required is not very big.
class A {
private int required1;
private int required2;
private int optional1;
private int optional2;
public A(int required1, int required2) {
this.required1 = required1;
this.required2 = required2;
}
#Builder(toBuilder = true)
public A setOptionals(int optional1, int optional2) {
this.optional1 = optional1;
this.optional2 = optional2;
return this;
}
}
And then construct it with
A a = new A(1, 2).builder().optional1(3).optional2(4).build();
Nice thing with this approach is that optionals can also have default value.
One approach I have used before was to group instance fields into Configuration fields and Session fields. Configuration fields go as class instances and are visible to the Builder, while Session fields go into a nested private static class and are accessed via a concrete final instance field (which the Builder will ignore by default).
Something like this:
#Builder
class XYZClientWrapper{
private String name;
private String domain;
private static class Session {
XYZClient client;
}
private final Session session = new Session();
private void initSession() {
session.client = ...;
}
public void foo() {
System.out.println("name: " + name);
System.out.println("domain: " + domain;
System.out.println("client: " + session.client);
}
}
To exclude field from builder, try using #Builder.Default