How do I use lombok #Builder to store different values? - java

I have the following JPA entity
#Data
#Builder
public class Post {
#Id
#GeneratedValue
UUID id;
#OneToMany
Set<PostTags> tags;
String content;
}
#Data
public class PostTag {
#Id
#GeneratedValue
UUID id;
#OneToOne
Post post;
String tag;
}
Using lombok #Builder I want to be able to do the following
Post post = Post.builder()
.tags("hello", "world")
.content("Hello world")
.build();
I am presuming I need a custom builder along the lines of
public static class PostBuilder {
private Set<String> myTags = new HashSet<>();
public PostBuilder tags(String... tags) {
myTags.addAll(Arrays.asList(tags));
return this;
}
}
From the documentation it appears there ObtainVia annotation that I can use, but I am not sure how to get around it (no example on the doc) and especially since I only want myTags to be a builder specific thing, and not be exposed on the main class itself.

ObtainVia only works for toBuilder, so that won't help much in this case.
I suggest the following approach.
First, add a factory method in PostTag, e.g. createTag(String). This method only sets tag in the instance it creates and leaves everything else null. Statically import it into the class where you want to use PostBuilder.
Next, use #Singular on tags. Then you can write:
Post post = Post.builder()
.tag(createTag("hello"))
.tag(createTag("world"))
.content("Hello world")
.build();
Finally, customize the build() method so that it first creates the Post instance (like an uncustomized build() method would) and then sets this newly created Post instance as post in all PostTag instances.
Have a look at the delomboked code to make sure you use the right builder class and method headers when customizing the builder.

You can use #Accessors for what you're asking:
Post
#Data
#Accessors(chain = true)
public class Post {
#Id
#GeneratedValue
private UUID id;
#OneToMany
private Set<PostTags> tags;
private String content;
public Post tags(String... tags) {
Arrays.stream(tags)
.map(tag -> PostTags.builder().tag(tag).build())
.forEach(this.tags::add);
return this;
}
}
PostTags
#Data
#Builder
public class PostTags {
#Id
#GeneratedValue
private UUID id;
#OneToOne
private Post post;
private String tag;
}
When you using #Accessors(chain = true), The setters will return this reference instead of void, and then your code will act this way:
Post post = new Post().setId(id).tags("aaa", "bbb");
If you want your code to be more similar to builder then add fluent value to the annotation: #Accessors(chain = true, fluent = true)
It will remove the set<Something> from the setters and just use the name of the fields, and then your code will look like this:
Post post = new Post().id(id).content("hello").tags("aaa", "bbb");

Related

Can't generate mapping method with no input arguments with Mapstruct

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

How to map a DTO to multiple entities?

I'm writing a Spring Application, which has two entities that are related by a one to many relationship, lets call them mother and kid.
When I create a mother entity via POST request, I want a kid entity be created automatically. Using the #OneToMany and #ManyToOne annotations, that works fine. At least, as long as I provide the kid information within the MotherService.
Here is my code
Mother.java
#Entity
#Table(name="mother")
public class Mother{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#OneToMany(mappedBy = "mother", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Kid> kidList = new ArrayList<>();
//constructor, getter, setter
private void addKid(Kid kid) {
this.kidList.add(kid);
kid.setMother(this);
}
}
Kid.java
#Entity
#Table(name="kid")
public class Kid{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "mother_id", nullable=false)
private Mother mother;
//constructor, getter, setter
}
MotherController.java
#RestController
#RequestMapping("mothers")
public class MotherController {
#Autowired
private MotherService motherService;
MotherController(MotherService motherService) {
this.motherService = motherService;
}
#PostMapping
Mother createMother(#RequestBody Mother mother) {
return this.motherService.createMother(mother);
}
}
MotherService.java
#Service
public class MotherService {
private MotherRepository motherRepository;
#Autowired
public MotherService (MotherRepository motherRepository) {
super();
this.motherRepository= motherRepository;
}
public Mother createMother(Mother mother) {
Kid kid = new Kid("Peter");
mother.addKid(kid);
return this.motherRepository.save(mother);
}
}
The repositories for mother and kid extend the JpaRepository without any custom methods so far.
My POST request is something like (using Postman)
{
"name":"motherName"
}
Now a mother is created with a name "motherName" and a kid with the name of "Peter".
My idea: Using a DTO
I now try to implement a DTO, that contains the mothers name and the kids name, map this information in the MotherService to the entities and save them via the corresponding repository, so I can define both names in the POST request.
motherDto.java
public class mother {
private String motherName;
private String kidName;
//getter, setter
}
So when I POST
{
"motherName":"Susanne",
"kidName":"Peter"
}
or even better
{
"mother": {
"name":"Susanne"
},
"kid": {
"name":"Peter"
}
}
a mother with name Susanne and a kid with name Peter are created.
My question is
How do I map a DTO to two entities?
Or do I not get something right? Is there an easier way to achieve my goal?
I know this is old and probably long solved, but let me offer a different take on the subject.
Another option would be to design a DTO solely for the purpose of creating the two entities you mentioned. You could call this MotherChildCreationDTO or something like that so the name already conveys its use and maybe create a REST-target consuming the DTO.
Asymmetric DTOs (receiving and sending) are an established pattern, and the DTOs are closely coupled to the REST controller any way.
First solution:
You can don't use DTO and send your JSON with same structure of Mother and kids and Jackson in Spring MVC deserialize it correctly for you.
{
id:2,
name:'sarah'
kidList:[{id:546,name:'bob'},{id:478,name:'tom'}]
}
Second solution:
If you want to different structure in JSON and Models and you can use Jackson annotation like #JsonProperty or #JsonDeserialize. Read this like for more information.
Third solution:
You can use DozzerMapper for complex mapping between your DTO and your Model. you define XML's file for mapping each model to your DTO and DozzerMapper map your DTO to your models.Read this link for more information.
You have 2 ways:
Map DTO to entities by yourself. In this case, you should create custom mapper and define how exactly DTO should be converted to entity. Then just inject and use your custom mapper in service.
Use one of existing mapper libraries. For example, good candidates are MapStruct and ModelMapper. You can find usage examples in corresponding getting started guides.

API Rest with Spring Boot

I'm seeing some videos about API Rest with Spring Boot and so far I've done some basics and when I tried to increase the complexity I'm getting caught.
My idea is in the Post / class, create a new class with students getting the following json:
{
"nome": "Primeira Serie - A".
"alunos": [
"João",
"José",
"Maria"
]
}
And return:
{
"id_classe": 101
}
It happens that it saves the class, but it does not save the students and I have no idea how to show only the id of the class.
I have created the following classes in Java:
Model
Classe.java
package com.example.classe.model;
//Import's suppressed
#Entity
#Table(name = "classe")
public class Classe {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nome;
#OneToMany(mappedBy = "classe")
private Set<Aluno> alunos = new HashSet<Aluno>();
//Get's e Set's suppressed
}
Aluno.java
package com.example.classe.model;
//Import's suppressed
#Entity
#Table(name = "aluno")
public class Aluno {
private static int tempID = 0;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nome;
#ManyToOne
#JoinColumn(name = "id_classe")
#JsonBackReference
private Classe classe;
public Aluno(String nome) {
tempID++;
this.id = tempID;
this.nome = nome;
}
public Aluno() {
}
//Get's e Set's suppressed
}
Repository
ClasseRepository.java
package com.example.classe.repository;
//Import's suppressed
#Repository
public interface ClasseRepository extends JpaRepository<Classe, Integer> {
public List<Classe> findAll();
}
Controller
ClasseController.java
package com.example.classe.controller;
//Import's suppressed
#RestController
#RequestMapping("/classe")
public class ClasseController {
#Autowired
private ClasseRepository classeRepo;
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Collection<Classe>> getClasse() {
return new ResponseEntity<>(classeRepo.findAll(), HttpStatus.OK);
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> salvarClasse(#RequestBody Classe classe) {
return new ResponseEntity<>(classeRepo.saveAndFlush(classe), HttpStatus.CREATED);
}
}
Am I doing everything wrong or did I not understand the concept? But I wanted to understand how to do it that way.
Thanks in advance.
Cesar Sturion
What you want to achieve is totally doable, but requires several changes.
I split my answer into 2 parts:
Save the students
There are several problems with saving:
On POST your incoming json deserialized into objects in which Classe has a reference to Anuli, but Anuli doesn't have a reference toClasse. To check it you can add a break point at the line: return new ResponseEntity<>(... , run in debug mode and check fields of Anuli in Classe. To fix it you can add #JsonManagedReference on aluni field in Classe. Related question
Hibernate can't save referenced objects by default. You have to save them one by one after saving your Classe object or just turn on Cascade persisting. Related question
So, to fix 1 and 2 Classe should have:
#OneToMany(mappedBy = "classe", cascade = CascadeType.PERSIST)
#JsonManagedReference
private Set<Aluno> alunos = new HashSet<Aluno>();
You have to remove custom id generation in Alumi (I am talking about static int tempID). Annotation #GeneratedValue will perfectly generate id for you as soon as you persist an object. This custom generation breaks Hibernate support. I even not talking about that it also breaks the app after restart, not threadsafe etc.
Return id only
On POST returned json represent what was returned in classeRepo.saveAndFlush(classe) so it's an object of Classe.
If you want to return exactly this:
{
"id_classe": 101
}
Then create new class like this:
public class ClasseIdVO {
#JsonProperty("id_casse")
private Integer id;
// Constructors, getter, setter
VO - means View Object, so this object only for representation, not for persisting, etc.
You can use field name id_casse, but it's against Java code convention, so better add #JsonProperty.
Also change your saving code to new ClasseIdVO(classeRepo.saveAndFlush(classe).getId())
Or you can just return id as a number: classeRepo.saveAndFlush(classe).getId()

Direct self-reference leading to cycle Superclass issue JSON

I have tried several things I found while searching but nothing helped or I did not implement it correctly.
Error I'm getting
Direct self-reference leading to cycle (through reference chain: io.test.entity.bone.Special["appInstance"]->io.test.entity.platform.ApplicationInstance["appInstance"])
Both these extend the base entity and in the base (super class) it has an appInstance as well.
Base entity looks similar to this
#MappedSuperclass
public abstract class BaseEntity implements Comparable, Serializable {
#ManyToOne
protected ApplicationInstance appInstance;
//getter & setter
}
Application entity looks like this
public class ApplicationInstance extends BaseEntity implements Serializable {
private List<User> users;
// some other properties (would all have the same base and application instance . User entity will look similar to the Special.)
}
Special entity
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "objectType")
#JsonIgnoreProperties({"createdBy", "appInstance", "lastUpdatedBy"})
public class Special extends BaseEntity implements Serializable {
#NotNull
#Column(nullable = false)
private String name;
#Column(length = Short.MAX_VALUE)
private String description;
#NotNull
#Column(nullable = false)
private Double price;
#OneToOne
private Attachment image;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = SpecialTag.class)
#CollectionTable(name = "special_tags")
#Column(name = "specialtag")
private List<SpecialTag> specialTags;
#Temporal(TemporalType.TIME)
private Date specialStartTime;
#Temporal(TemporalType.TIME)
private Date specialEndTime;
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = WeekDay.class)
#CollectionTable(name = "available_week_days")
#Column(name = "weekday")
private List<WeekDay> availableWeekDays;
#OneToMany(mappedBy = "special", cascade = CascadeType.REFRESH)
private List<SpecialStatus> statuses;
#OneToMany(mappedBy = "special", cascade = CascadeType.REFRESH)
private List<SpecialReview> specialReviews;
#Transient
private Integer viewed;
private Boolean launched;
#OneToMany(mappedBy = "special")
private List<CampaignSpecial> specialCampaigns;
#Override
#JsonIgnore
public ApplicationInstance getAppInstance() {
return super.getAppInstance();
}
}
All entities in Special inherits from BaseEntity which contains AppInstance
then i have a method to get the special
#GET
#Path("{ref}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(value = MediaType.TEXT_PLAIN)
public Special findByGuestRef(#PathParam("ref") String pRefeference) {
// find the special and return it
return special;
}
On the special entity I tried the following
Added jsonIgnoreProperties
Added an override for appInstance to annotate with #JsonIgnore
#JsonIdentityInfo
links for the above
https://stackoverflow.com/a/29632358/4712391
Jackson serialization: how to ignore superclass properties
jackson self reference leading to cycle
none of those solutions works. Am I doing something wrong?
Note: Would it also just be possible to edit special, since the other entities are in a different package and would not like to edit them.
Usually excluding attributes in a response is as easy as adding a #JsonIgnore annotation to their getters, but if you don't want to add this annotation to a parent class, you could override the getter and then add the annotation on it:
public class Special extends BaseEntity implements Serializable {
...
#JsonIgnore
public ApplicationInstance getAppInstance() {
return this.appInstance;
}
...
}
NOTE: As there are several frameworks, make sure that you are using the correct #JsonIgnore annotation or it will be ignored, see this answer for instance.
Another option, more "manual", is just creating a bean for the response which would be a subset of the Special instance:
#GET
#Path("{ref}")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(value = MediaType.TEXT_PLAIN)
public SpecialDTO findByGuestRef(#PathParam("ref") String pRefeference) {
// find the special and return it
return new SpecialDTO(special);
}
public class SpecialDTO {
//declare here only the attributes that you want in your response
public SpecialDTO(Special sp) {
this.attr=sp.attr; // populate the needed attributes
}
}
To me, problem seems to be in the Special object and the fields being initialized in it.
I guess that there is a circular reference detected when serialisation happens.
Something similar to:
class A {
public A child;
public A parent;
}
A object = new A();
A root = new A();
root.child = object;
object.parent = root;
In the above code, whenever you will try to seralize either of these objects, you will face the same problem.
Note that public fields are not recommended.
I'll suggest to peek into your Special object and the references set in it.

Explicit constructor using Lombok?

I'm rewriting some messy code that manages a database, and saw that the original programmer created a class mapped to the database like so:
(I've removed unnecessary code that has no purpose in this question)
#Entity
#Data
#EqualsAndHashCode(callSuper = false, of = { "accessionCode", "header", "date" })
#SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry implements Serializable {
#Id
#NaturalId
#NotEmpty
#Length(max = 4)
private String accessionCode;
#NaturalId
#NotEmpty
private Date date;
#NaturalId
// We allow for the header to be 'null'
private String header;
private Boolean isValidDssp;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
protected PDBEntry(){}
public PDBEntry(String accessionCode, String header, Date date){
this.accessionCode = accessionCode;
this.header = header;
this.date = date;
}
}
I am still a beginner at Hibernate and using Lombok, but wouldn't this do the same thing and wouldn't Lombok automatically create the needed constructor for you?
#Entity
#Data
#SuppressWarnings("PMD.UnusedPrivateField")
public class PDBEntry implements Serializable {
#Id
#NaturalId
#NotEmpty
#NonNull
#Length(max = 4)
private String accessionCode;
#NaturalId
#NotEmpty
#NonNull
private Date date;
#NaturalId
// We allow for the header to be 'null'
private String header;
private Boolean isValidDssp;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdated = new Date(System.currentTimeMillis());
}
Also, the original programmer of this code says he allows for the header to be 'null', yet he explicitly created a constructor that needs a value for header. Am I missing something or is this a bit contradictory?
Have a look at #NoArgsConstructor, #RequiredArgsConstructor, #AllArgsConstructor.
The constructor behavior of #Data is like #RequiredArgsConstructor:
#RequiredArgsConstructor generates a
constructor with 1 parameter for each
field that requires special handling.
All final fields get a parameter, as
well as any fields that are marked as
#NonNull that aren't initialized where
they are declared.
Given that none of your fields are either final or #NonNull, this will result in a no-argument constructor. However, this is not the most expressive way to achieve this behavior.
What you'll probably want in this case is a #NoArgsConstructor (optionally combined with a #AllArgsConstructor), to clearly communicate the intended behavior, as is also indicated in the documentation:
Certain java constructs, such as
hibernate and the Service Provider
Interface require a no-args
constructor. This annotation is useful
primarily in combination with either
#Data or one of the other constructor
generating annotations.
That bit is contradictory you're right. I've not used Lombok before but with hibernate if you want to be able to create a bean and persist you need the default constructor as given above as far I was aware. It uses Constructor.newInstance() to instantiate new objects.
Here is some hibernate documentation which goes into more detail.
Hibernate Documentation
If you are using #Data with a #NonNull field and still want a noargs-constructor, you might wanna try to add all 3 annotation together
#NoArgsConstructor
#RequiredArgsConstructor
#AllArgsConstructor
Apparently an old intelliJ bug which I did replicate in Eclipse Kepler and lombok v0.11.4
#NoArgsConstructor,
#RequiredArgsConstructor,
#AllArgsConstructor
Generate constructors that take no arguments, one argument per final / non-null field, or one argument for every field. Read this lombok-project
#Data
#RequiredArgsConstructor /*Duplicate method Someclass() in type Someclass*/
#NoArgsConstructor(access=AccessLevel.PRIVATE, force=true) /*Duplicate method Someclass() in type Someclass*/
#Entity
public class Someclass {
#Id
private String id;
private String name;
private Type type;
public static enum Type { X , Y, Z}
}
Fixed it by making member variables final
#Data
#RequiredArgsConstructor
#NoArgsConstructor(access=AccessLevel.PRIVATE, force=true)
#Entity
public class Someclass {
#Id
private final String id;
private final String name;
private final Type type;
public static enum Type { X , Y, Z}
}

Categories

Resources