Spring abstract class with final fields and inheritance with lombok's #SuperBuilder - java

I am currently trying to remove some boilerplate code with lombok but have some trouble.
I have an abstract class AbstractParent,
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode
#ToString
#Getter
#Setter
public abstract class AbstractParent {
private final field1;
private final field2;
then I have a Child Class like this
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
public abstract class Child extends AbstractParent {
And I also have some classes extending the Child class
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
#Component
public abstract class ExtendedChild extends Child {
private final field1;
private final field2;
Since lombok can't use super in a constructor, I tried the #SuperBuilder Annotation instead of defining the Constructors manually but can't get the Application to start. Am I missing something completely? Is this even possible with lombok and spring?
The Error is:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.fu.extendedChild required a bean of type 'com.fu.extendedChild$extendedChildBuilder' that could not be found.
Action:
Consider defining a bean of type 'com.fu.extendedChild$extendedChildBuilder' in your configuration.

I was able to reproduce your problem with this code
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode
#ToString
#Getter
#Setter
public abstract class AbstractParent {
private final String field1;
private final String field2;
}
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
abstract class Child extends AbstractParent {
}
#SuperBuilder(toBuilder = true)
#EqualsAndHashCode(callSuper = true)
#ToString(callSuper = true)
#Component
class ExtendedChild extends Child {
private final String field1;
private final String field2;
}
What the #SuperBuilder does here on class ExtendedChild is
protected ExtendedChild(ExtendedChildBuilder<?, ?> b) {
super(b);
this.field1 = b.field1;
this.field2 = b.field2;
}
So it says you need an ExtendedChildBuilder instance in order to build an ExtendedChild instance. In order words, you have to have a builder in your spring context to be able to create your object.
This is not a good idea since a builder is stateful and not thread-safe. Furthermore, the builder pattern is here to be able to provide values whenever you need them before constructing your object. Using a builder as a Spring bean denies that advantage.
If this is immutability you want to achieve, then using plain old constructors with the right parameters is way better (and when done right, this is not boilerplate code, this is good design).
Then, Spring injection will be a child's play.
Please do not trade complexity for the sake of writing less code :)

Related

How can I make javax validation both of sub class and super class?

I'm using Spring framework,
and I faced the inheritance problem when I write Controller logic.
First of all,
this is my Controller code snippet
#PostMapping("/pay/detail")
public ResponseEntity<PayDetail.Response> getPayDetail(
#Valid PayDetail.Request payDetail
) {
... some code
}
and PayDetail class looks like this
public class PayDetail {
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public static class Request extends CommReqForm {
#NotNull(message = "Not null please")
private String work_type;
}
}
and CommReqForm
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CommReqForm {
#NotEmpty(message = "servicecode not empty")
private String servicecode;
#NotEmpty(message = "reqtype not empty")
private String reqtype;
}
I wish that I can validate both of PayDetail.Request and CommReqForm classes but It makes validation just only PayDetail.Request class.
How can I solve this problem?
#Valid cannot validate super class. I want to make both of sub class and super class validation.

Hibernate throws NotWritablePropertyException when saving entity

This happened to me other times, but this time the reason is different.
I reduced the scenario where it can be reproduced down to two entities, Child and Parent:
Child:
#Table(name = "Childs")
#Entity
#IdClass(KeyChild.class)
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Child {
#Id
#ManyToOne
private Parent parent;
#Id
private int id0;
}
Parent:
#Table(name = "Parents")
#IdClass(KeyParent.class)
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Getter
#Setter
public class Parent {
#Id
private String id1;
#Id
private int id2;
}
The composite keys:
public class KeyChild implements Serializable {
private KeyParent parent;
private int id0;
}
and
public class KeyParent implements Serializable {
private String id1;
private int id2;
}
The problem happens when trying to save a Child (specifying the parent as you would spect).
An example controller:
#Controller
public class ParticiparController {
#Autowired
ParentRepository parentRepository;
#Autowired
ChildRepository childRepository;
#GetMapping("/test/")
public String evento() {
Parent p = Parent.builder().id("test").id2(1).build();
parentRepository.save(p);
childRepository.save(Child.builder()
.parent(p)
.id(0)
.build());
return "test";
}
}
The save method of the child repository throws:
org.springframework.beans.NotWritablePropertyException: Invalid property 'id1' of bean class [com.example.myProject.Entities.Keys.KeyParent]: Bean property 'id1' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
I also tried without Lombok (just generating the getters and setters), but I got same result. I'm pretty sure setters are not the problem.
Update: There were, but not the entity ones... It seems that are only required in some cases.
As it suggested in the error message you should have getters/setters in the KeyParent which correspond to the appropriate getters/setters of the Parent entity fields annotated by the #Id.

How to configure lombok to generate Getters/Setter for static members also when annotated on class

I have a class for all static members. The number of static members is more than 10 (which may increase with time).
I am using lombok and I want to generate Getters/Setters for all static members using single #Getter and #Setter annotations on class as we do for non-static members.
I know that
You can also put a #Getter and/or #Setter annotation on a class. In
that case, it's as if you annotate all the non-static fields in that
class with the annotation.
I also know that
We can annotate static fields individually using #Getter #Setter to generate Getters/Setters for static fields.
But this looks ugly and I want to make my class look as clean as possible.
Is there any way I can configure / Override #Getter and #Setter annotation so that I can annotate the class and it generate Getters and Setters for all members including static and non-static members, after all, what do those methods do is return the mentioned variable.
To be more precise, I want the following code snippet to generate Getters and Setters for all class variables-
#Getter
#Setter
public class myClass {
private static String d;
private static SomePojo c;
private String a;
private Integer b;
private SomeClass d;
}
Add #Getter to the static member itself and it should work.
#Getter
private static final String DEFAULT_VAL = "TEST";
For static fields you have to add #Getter to the specific field:
#Getter
#Setter
public class Task {
#Getter
private static int numberOfTasks;
#Getter
private static int taskId;
private String taskName;
private Integer executionTime;
}

Why #Data and #Builder doesnt work together

I have this simple class
public class ErrorDetails {
private String param = null;
private String moreInfo = null;
private String reason = null;
...
}
After refactoring, I added #Data and #Builder, but all the instantiations doesn't work any more
ErrorDetails errorDetails = new ErrorDetails();
'ErrorDetails(java.lang.String, java.lang.String, java.lang.String)'
is not public in
'com.nordea.openbanking.payments.common.ndf.client.model.error.ErrorDetails'.
Cannot be accessed from outside package
If I removed #Builder, then it will work fine,
Why I cannot use #Data and #Builder together?
Lombok's #Builder must have #AllArgsConstructor in order to work
Adding also #AllArgsConstructor should do
Under the hood it build all fields using constructor with all fields
applying #Builder to a class is as if you added #AllArgsConstructor(access = AccessLevel.PACKAGE) to the class and applied the #Builder annotation to this all-args-constructor. This only works if you haven't written any explicit constructors yourself.
The full config should be :
#Data
#Builder(toBuilder = true)
#AllArgsConstructor
#NoArgsConstructor
class ErrorDetails {
private String param; // no need to initiate with null
private String moreInfo;
private String reason;
}

Java Double Brace Initialization causes IllegalArgumentException: Unknown entity

I just try to create a CRUD Web Application with Spring Boot and I found that there is a problem with using Java Double Brace Initialization in the framework.
Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Unknown entity: com.example.service.impl.FileImageServiceImpl$1; nested exception is java.lang.IllegalArgumentException: Unknown entity:
I have the #Entity class:
#Entity
public class RandomEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
//Getter and Setter
}
A #RestController
#RestController
public class RandomController{
#Autowired
private RandomRepository randomRepository;
#GetMapping("/create")
public String create(){
RandomEntity rdEntity = new RandomEntity(){{
setName("Bla Bla");
}};
return randomRepository.save();
}
}
Here is the repository
public interface RandomRepository extends CrudRepository<RandomEntity, Long> {
}
But when I change Java Double Brace Initialization to Normal Initialization, the Application run properly.
Do you know why is that?
Thank you so much!
It may look like a nifty shortcut that just calls the constructor of your class followed by some initialization methods on the created instance, but what the so-called double-brace initialization really does is create a subclass of your Entity class. Hibernate will no longer know how to deal with that.
So try to avoid it. It has a lot of overhead and gotchas just to save you a few keystrokes.
I just want to complete the answer of #Thilo, If you want a clean code use Builder design pattern, now you can implement this Design easily via Lombok library, so you can Just annotate your Entity like so :
#Entity
#Getter #Setter #NoArgsConstructor #AllArgsConstructor
#Builder(toBuilder = true)
class RandomEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
So there are really some cool annotations, for example #Getter and #Setter to avoid all that getters and setters, #Builder(toBuilder = true) to work with builder design so your controller can look like :
#GetMapping("/create")
public RandomEntity create() {
// Create your Object via Builder design
RandomEntity rdEntity = RandomEntity.builder()
.name("Bla Bla")
.build();
// Note also here save should take your Object and return RandomEntity not a String
return randomRepository.save(rdEntity);
}

Categories