I want use BeanCopier to do the property copying between the following two porous
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
#TableName("t_order")
public class Order extends BaseEntity {
private static final long serialVersionUID=1L;
private Long userId;
private Integer amount;
private Long productId;
}
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class OrderDTO {
private Long userId;
private Integer amount;
private Long productId;
}
for the following codes,
public static void main(String[] args) {
Order order = Order.builder().productId(3333L).userId(9999L).amount(32).build();
OrderDTO orderDTO = new OrderDTO();
BeanCopier orderCopier = BeanCopier.create(Order.class, OrderDTO.class, false);
orderCopier.copy(order, orderDTO, null);
JSONUtils.toJSONString(orderDTO);
}
the properties of orderDTO are not set, the fields of orderDTO are all null, what is wrong?
ohh there is simple thing missing , please add getters and setters , BeanCopier internally uses ReflectUtils to find getters and setters.
Please try and add those and then test.
There also a alternative -
you can simple use Spring's BeanUtils and it's copyProperties - there are multiple options available
Spring BeanUtils
You can simply use it as
BeanUtils.copyProperties( sourceBean , targetBean );
You can find different examples HERE
Related
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
public class Example {
public ExampleTwo exampleTwo;
#lombok.Data
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
public static class ExampleTwo {
private SomeData someData;
private AnotherField anotherField;
}
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder(toBuilder = true)
public class SomeData {
private String specialId;
private String firstName;
private String lastName;
}
So I retrieved an Example instance and made a deep copy to duplicate it. But I want to be able to set one of the fields of the copied object which is specialId from the SomeData nested class. My current working implementation is this:
SomeData someData = example.getExampleTwo().getSomeData().toBuilder().specialID("SPE_1").build();
Example.ExampleTwo exampleTwo = example.getExampleTwo().toBuilder().someData(someData).build();
Example duplicateExample = example.toBuilder().exampleTwo(exampleTwo).build();
Do you have thoughts on ways to make this cleaner without having to go through these additional steps? Would prefer it to be easier to read. I'm avoiding Serializable implementation and declaring it as a Cloneable interface since I've read to avoid those. Last resort would be to use a library.
#With might be helpful:
Example example = Example.builder().build();
Example.ExampleTwo exampleTwo = example.getExampleTwo();
SomeData someData = exampleTwo.getSomeData();
return example.withExampleTwo(
exampleTwo.withSomeData(
someData.withSpecialId("SPE_1")
)
);
I am trying to map DTO to the corresponding #Entity in the service layer.
Condition may be of a set of types: Amount, Title, Date. Each condition, except Amount, has a unique predefined set of clauses.
TitleCondition: includes, startsWith
DateCondition: from, until
The idea is to use common Condition entity with #Inheritance(strategy= InheritanceType.TABLE_PER_CLASS).
The 2 problems I see with this code is:
It is unclear how to properly set data
data type is Object
Is there a way to use convenient Lombok's #Builder with given mapping? What would be the simpler and better way to map dto to entity?
Service:
#Service
public class FilterService {
private Condition convertConditionDtoToEntity(ConditionDto conditionDto) {
Type type = typeRepository.findFirstByName(conditionDto.getType())
.orElseThrow(UnsupportedOperationException::new);
Clause clause;
if (conditionDto.getClause() != null) {
clause = clauseRepository.findFirstByName().orElseThrow(UnsupportedOperationException::new);
}
if (conditionDto.getType().equals("amount")) {
return AmountCondition.builder().type(type).data(???).build();
} else if (conditionDto.getType().equals("title")) {
return TitleCondition.builder().type(type).clause(clause).data(???).build();
} else if (conditionDto.getType().equals("date")) {
return DateCondition.builder().type(type).clause(clause).data(???).build();
} else {
throw new UnsupportedOperationException();
}
}
}
Condition
#Getter
#SuperBuilder
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Inheritance(strategy= InheritanceType.TABLE_PER_CLASS)
public abstract class Condition {
#Id
#GeneratedValue
private long id;
#ManyToOne
private Filter filter;
#Getter
#ManyToOne
public Type type;
public abstract Object getData();
}
DateCondition
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class DateCondition extends Condition {
#Column
int clauseId;
#Column
#Temporal(TemporalType.DATE)
Date date;
#Getter
#ManyToOne
private Clause clause;
#Override
public Object getData() {
return date;
}
}
TitleCondition
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class TitleCondition extends Condition {
#Column
int clauseId;
#Column
String title;
#Getter
#ManyToOne
private Clause clause;
#Override
public Object getData() {
return title;
}
}
AmountCondition
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class AmountCondition extends Condition {
#Column
int amount;
#Override
public Object getData() {
return amount;
}
}
Clause
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Clause {
#Id
#GeneratedValue
private long id;
#Column
String name;
#OneToMany(mappedBy = "clause")
private Set<Type> types = new HashSet();
}
Type
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Type {
#Id
#GeneratedValue
private long id;
#Column
String name;
#ManyToOne
private Clause clause;
#OneToMany(mappedBy = "type")
private Set<Condition> conditions;
}
I have two forms that I use a lot and save a lot of time.
first: transform your model into json and convert the json to object the Mapper class (this link will help https://www.baeldung.com/jackson-object-mapper-tutorial)
second: spring has some cool functions about it. an example would be the BeanUtils.copyProperties function (source, target);
data problem: the data field does not exist for this reason you do not need to set it.
Another problem that I was able to notice is that their properties are all defaulted so set them as private.
Let's say that I have classes that illustrate the problem of songs and votes voted for.
User.java
#EqualsAndHashCode
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Builder
#Document(collection = "users")
public class User {
#Id private String id;
private String username;
private String email;
private Integer age;
private String password;
#DBRef(db = "interview", lazy = true)
#EqualsAndHashCode.Exclude
#ToString.Exclude
private Set<Song> songs;
}
Song.java
#Data
#AllArgsConstructor
#NoArgsConstructor
#Builder
public class Song {
#Id private String id;
private String title;
private String author;
private SongGenre songGenre;
Set<Vote> votesOfSong;
}
Vote.java
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Document(collection = "votes")
#Getter
public class Vote {
#Id private String id;
private User user;
private Song song;
private VoteKind voteKind;
#Default private LocalDateTime dateOfVote = LocalDateTime.now();
}
and
VoteKind.java
#RequiredArgsConstructor
#Getter
public enum VoteKind {
LIKE(1),
DISLIKE(-1);
private final Integer voteValue;
}
I want to create a method which will return the most common SongGenre, SongTitle and number of votes for a particular song.
So far I have a methods like:
public Flux<Map<Song, SongGenre>> theMostCommonSongForSongGenre(SongGenre songGenre) {
voteRepository
.findAll()
.collect(
Collectors.toMap(vote -> vote.getSong().getSongGenre(), this::getSumOfVotesForVote))
.map(Map::entrySet)
.flatMapMany(Flux::fromIterable)
.filter(stringListEntry -> stringListEntry.getKey().equals(songGenre))
.sort(Map.Entry.comparingByKey(Comparator.reverseOrder()))
.collect(Collectors.toMap(o -> o.getKey(), o -> o.getValue()));
}
and the helper method:
private int getSumOfVotesForSong(Vote vote) {
return vote.getSong().getVotesOfSong().stream()
.mapToInt(voteKind -> vote.getVoteKind().getVoteValue())
.sum();
}
There are two problems here that I cannot quite solve.
How to limit the result of my Flux to for instance 1 or 10 records. In a conventional way, in Java Stream API, I can use methods findFirst or limit but here there is no another equivalent method. The only method I can call is limitRate but it has another predestination.
How to convert current solution to return SongGenre, SongTitle and number of votes for a particular song. Is there any chance to return MultiMap for above parameters or I should use groupingBy and return a custom object with songGenre and songTitle as key and as value Integer which will be equivalent to number of votes.
EDIT:
Core java stream solution can be either desirable.
I will be grateful for suggestions on how to reach a goal.
If you're using reactive you generally want to avoid things like collect() as it's blocking. You can call collectList() on the flux to get a mono of that list.
Flux has a take() method to limit the results.
JsonDeserialize not working when objectmapper readvalue for inherited properties.
Vehicle Class
#Getter
#Setter
#JsonDeserialize(builder = Vehicle.VehicleBuilder.class)
#Builder(builderClassName = "VehicleBuilder", toBuilder = true)
public class Vehicle{
private String name;
private String noOfTyres;
#JsonPOJOBuilder(withPrefix = "")
public static class VehicleBuilder{
}
}
Car class
#Getter
#Setter
#JsonDeserialize(builder = Car.CarBuilder.class)
#Builder(builderClassName = "CarBuilder", toBuilder = true)
public class Car extends Vehicle {
private String carType;
#JsonPOJOBuilder(withPrefix = "")
public static class CarBuilder extends VehicleBuilder {
}
}
I don't want to create #NoArgsConstructor ,#AllArgsConstructor in both classes.
My issue when Car car = om.readValue(jsonValue,Car.class);
When I parse Json to java object the parent class properties are not setting properly.
As of now I'm using #NoArgsConstructor ,#AllArgsConstructor for work around for the use case.
Is there any way to use it along with #JsonDeserialize and #JsonPOJOBuilder?
The problem with the code is that it assumes that builders in inherited classes will set the parent properties as well. Unfortunately, they don't do that out of the box. However, this is something that can be achieved with Lombok but requires some additional code, as described in this post.
A complete solution could look as follows.
Parent Class
#Getter
#Setter
#JsonDeserialize
#Builder(builderClassName = "VehicleBuilder", builderMethodName = "vehicleBuilder")
public class Vehicle {
private String name;
private String noOfTyres;
}
Child Class
#Getter
#Setter
#JsonDeserialize(builder = Car.CarBuilder.class)
public class Car extends Vehicle {
private String carType;
#Builder
public Car(String name, String noOfTyres, String carType) {
super(name, noOfTyres);
this.carType = carType;
}
#JsonPOJOBuilder(withPrefix = "")
public static class CarBuilder extends VehicleBuilder {
}
}
Notice that the builder on the extending class is achieved by supplying a constructor with the #Builder annotation. Also take notice that the extending class does not set annotation parameter toBuilder=true as that will require access to parent properties which are private. This can be achieved by setting parent class properties to protected.
I’m starting my very first steps with Mapstruct mapper. I want to map a JPA data entity class to a DTO class. This is my source class:
#Entity
#Data
#Table(name = "projects")
public class Project {
#Id
private Long Id;
private String projectName;
private String description;
#OneToMany(mappedBy = "project")
List<Sprint> sprints;
#OneToMany(mappedBy = "project")
List<Epic> epics;
#OneToMany(mappedBy = "project")
List<Story> stories;
public Project(Long id, String projectName, String description) {
Id = id;
this.projectName = projectName;
this.description = description;
}
}
This is my target class:
#Data
#AllArgsConstructor
public class ProjectDTO {
private Long Id;
private String projectName;
private String description;
}
The #Data annotation is from Lombok.
I want to make a mapper to map the Project to ProjectDTO, the attributes like sprints, epics, stories SHOULD NOT be included in ProjectDTO. This is my mapper interface:
#Mapper
public interface ProjectMapper extends Mapper {
ProjectMapper INSTANCE = Mappers.getMapper(ProjectMapper.class)
ProjectDTO projectToProjectDTO(Project project);
}
When I try to build it, this is the error message I got:
[ERROR] Can't generate mapping method with no input arguments.
I guess it’s related to the missing properties in ProjectDTO, but don’t know to solve it. With the #Mapping, I cannot do it like:
#Mapping(source=“sprints”, target= null)
Any help would be appreciated!
Add the '#NoArgConstructor' as well. MapStruct cannot (yet) deal with constructing objects via constructor. Another option would be using '#Builder' in stead if your objects are truly immutable
You should not extend the annotation Mapper. It is enough when you just use it at the type declaration level of your interface