How do I use a JPARepository inside of a PrePersist Annotation? - java

I have an alert table which is transactional and an alert type table which is master. I would like to send out an email whenever an alert is added to the table, so I figured I would use PrePersist. However, in my email, I want to include some information that is included in the alert type table.
I have tried to add a reference to the AlertTypeRepository in the Alert class but I can't because my alert class is a #Table and alertTypeRepository is not a column.
Below is my Alert class
#Entity
#Table
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Alert {
#Id
#GeneratedValue
int id;
#Column
String name;
#Column
String alertTypeId;
#Column
String detailedMessage;
#Column
String status;
#Temporal(TemporalType.TIMESTAMP)
Date time;
}
Below is my AlertType class
#Entity
#Table
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class AlertType {
#Id
#GeneratedValue
int id;
#Column
String name;
#Column
String header;
#Column
String footer;
#Column
String summary;
#Column
String deliveryType;
#Column
Boolean active ;
#Column
String recipients;
}
I would like to have a PrePersist function inside of the Alert class. That allows me to access its corresponding header and footer from the AlertType class.

I figured out a solution so I hope this helps anyone facing a similar issue. Basically I had to create an EntityListener to the Alert class and then add the following class.
#Component
public class AlertListener {
static AlertTypeRepository alertTypeRepository;
#Autowired
public void init(AlertTypeRepository alertTypeRepository)
{
this.alertTypeRepository = alertTypeRepository;
}
#PrePersist
public void prePersist(Alert alert) {
List<AlertType> alertType= this.alertTypeRepository.findAll();
}
}

As I know the are two approaches to archive the purpose. Your alterType is not managed by Spring .
Define a JPA EntityListener and apply it on your entity class, which does not seem to interest you.
The second approach, annotated your entity with Spring #Configurable annotation:
#Configurable(preConstruction = true)
class AlterType{
#Inject YourRepository bean as normal.
}
To make it work. Firstly you have to add aspectj related jars into your project dependencies. Secondly you can choose load-time weaving or compile-time weaving to handling the injection for you class.
There is an example of aspectj compiler config in Maven can be used for compile-time weaving(note, just for aspectj compiler maven plugin config, I did not use #Configurable here.).

Related

How do I create a safe Lombok JPA entity?

I have an #Entity with 20 fields including the index and a timestamp updated by Hibernate:
#Entity
public class MyEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#UpdateTimestamp
private LocalDateTime updatedTime;
private String ....
private String ....
I have a default constructor for Hibernate and a secondary constructor to set everything but the id and updatedTime.
I don't need (or want) setters for id or updatedTime because I only want Hibernate to set them, and it does that with reflection.
I wanted to try out Lombok to see if I could avoid a lot of boilerplate involved here but #Data adds both getters and setters and doesn't create the same constructors.
I'm also concerned that Lomboks generated equals/hashCode and toString methods can cause subtle problems with Hibernate.
This will mean I will have to use a combination of the other Lombok annotations to do this.
How do I safely create an Entity using Lombok like this?
Am I going to have to use a mixture of annotations and manual methods?
Some lombok annotations like #EqualsAndHashCode and #ToString have Exclude option. But neither #Data nor #AllArgsConstructor has a similar option.
But #Data generates setters for all fields for which a setter is not already defined. So you would define a setter as below for the required fields, which does nothing.
private void setId(Long id) {
// Do nothing
}
Instead of the #AllArgsConstructor, you could either use #RequiredArgsConstructor, but annotate all the fields to be in the constructor with #NonNull (or the field should be final).
Refer this answer for RequiredArgsConstructor.
My suggested approach : Another way would be to use #Builder annotation along with #AllArgsConstructor(access = AccessLevel.PRIVATE). (NOTE : Builder by default adds a private all argument constructor, but this is done only if there are no other constructors. But in your case, a default constructor exists and you need to explicitly mention the all args annotation.)
This would prevent the use of the constructor from outside, but at the same time allow you to create objects using the builder. At this point, you could set the values to id and updateTime using the builder. To prevent this you need to add the below code as well.
public static class MyEntityBuilder {
// access is restricted using
// these private dummy methods.
private MyEntityBuilder id(Long id) {
return this;
}
private MyEntityBuilder updateTime(LocalDateTime time) {
return this;
}
}
So, even though it is not possible to achieve your requirement directly, you could do so by adding two dummy setter methods and another two dummy methods within the builder class.
we have #NoArgsConstructor #AllArgsConstructor for generating constructor with lombok.
this is how i create them.
#Entity
#Table(schema = "S25", name = "bank")
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Getter
#Setter
public class Bank {
#Id
#SequenceGenerator(name = "bankEntitySeq", sequenceName = "SEQ_BANKS", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bankSeq")
#Column(name = "bank_id")
private Long bankId;
#Column(name = "bank_name")
private String bankName;
#Column(name = "created_on")
private Date createdOn =
new Date(); //Date.from(Instant.now().atZone(ZoneId.of("UTC")).toInstant());
}

Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object

I am getting below error while using lombok and even it doesn't allow me to set id and version while creating student instance.
Multiple markers at this line
- overrides com.example.demo.IModel.canEqual
- Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is
intentional, add '#EqualsAndHashCode(callSuper=false)' to your type.
- overrides com.example.demo.IModel.hashCode
- overrides com.example.demo.IModel.toString
- overrides com.example.demo.IModel.equals
IModel
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class IModel {
private String id;
private String version;
}
Student
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Student extends IModel{
private String firstName;
private String lastName;
}
In the main method, it doesn't allow me to set the value of Id and version field
Student s = Student.builder().firstName("Adam").lastName("Kerr").build();
Edit-1
#sfiss - As suggested, now I changed like below, but now I am not able to set firstName and lastName, only cab set id and version
Student.java
#Data
#Builder(builderMethodName = "studentBuilder")
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Student extends IModel {
#NotEmpty(message = "{email.notempty}")
#Email
private String firstName;
private String lastName;
public Student(final String firstName, final String lastName, final String id, final String version) {
super(id, version);
this.firstName = firstName;
this.lastName = lastName;
}
}
IModel.java
#Builder
#Data
#NoArgsConstructor
#AllArgsConstructor
public class IModel {
private String id;
private String version;
}
There are multiple problems here, all of them relating to using lombok with inheritance:
Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is
intentional, add '#EqualsAndHashCode(callSuper=false)' to your type.
The warning is given by #Data because that usually generates equals/hashcode without the call to super. Fix it by adding #EqualsAndHashCode(callSuper = true).
The #Builder gives you a compile warning because it will generate two static methods with the same name in both the super- and the subclass. Fix it by defining #Builder(builderMethodName = "studentBuilder") on Student.
You won't be able to set superclass properties on you studentBuilder because your superclass and subclass have a default constructor. Fix it by creating a constructor and moving the #Builder annotation to it (i.e. annotate the constructor with #Builder, not the class):
Code:
#Builder(builderMethodName = "studentBuilder")
public Student(
final String firstName,
final String lastName,
final String id,
final String version) {
super(id, version);
this.firstName = firstName;
this.lastName = lastName;
}
Call your builder with the correct method (IModel.builder() vs Student.studentBuilder()):
Student.studentBuilder().firstName("Name").build();
I also want to add some improvements to the above solution. While I like lombok as a tool (I really don't need to read that much boilerplate), the first solution to preventing boilerplate is to think whether you need all those getters and setters and ask yourself these questions:
Do you want bags of data? It is fine for some use cases, in others you want objects more in the sense of OOP, i.e. don't expose your state but behavior.
Do you really need mutability? If not, prefer #Value.
Do you really need both constructor types (especially the no-args-constructor)? They are part of the problem here. Sometimes you need them for frameworks (proxies, reflection, ...) to work properly.
More specific to your code: You prefixed the superclass with "I" but it is not an interface. If it is meant as an abstract class, declare it abstract and don't give it a #Builder.
You can use #sfiss solution
or
You can use #Getter and #Setter annotations instead of #Data annotation.
The warning from building a Spring Boot project
When I built my Spring boot project, I got 20 warnings about a same thing. The warning shows: Generating equals/hashCode implementation but without a call to superclass
Description of the warning
This warning is from lombook, it happens when we inherit a child class from parent class by using #Data #ToString #EqualsAndHashCode, IDE will trigger the warning: Generating equals/hashCode implementation but without a call to superclass .
Solution
There are two solutions:
add annotation #EqualsAndHashCode(callSuper = true) on the class
create lombook config file in the project root path: src/main/java. Note: this solution requires the version of lombook > 1.14.
I recommend the solution 2, since you will not need to add the annotation to all the required classes.
To impletement the solution, you need to create lombok.config in the path of src/main/java. If you have more than one packages, you may need to create multiple config files.
The content of the config file includes:
config.stopBubbling=true
lombok.equalsAndHashCode.callSuper=call
When we rebuild our project, you will not get these warnings anymore.
Cheers!
I had the same problem and i resolved it in this way hope it helps you.
That help you also with abstract classes
Student.java
#Data
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode(callSuper = true)
public class Student extends IModel {
#NotEmpty(message = "{email.notempty}")
#Email
private String firstName;
private String lastName;
#Builder
public Student(final String firstName, final String lastName, final String id, final String version) {
super(id, version);
this.firstName = firstName;
this.lastName = lastName;
}
}
IModel.java
#Data
#NoArgsConstructor
#AllArgsConstructor
public class IModel {
private String id;
private String version;
}
The error says If this is
intentional, add '#EqualsAndHashCode(callSuper=false)' to your type.
So, add #EqualsAndHashCode(callSuper = false) to solve the problem.

How to access Builder() of a JPA entity using Lombok with Spring Boot?

This is my entity class. I have used #Builder annotation. The Lombok version is 1.16.16
#Builder
#AllArgsConstructor
#NoArgsConstructor
#RequiredArgsConstructor
#Data
#Entity
#EqualsAndHashCode
#ToString
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String author;
}
Now, when I am trying to call the Builder() method in the main class, it is not getting resolved.
Stream.of("Post one", "Post two").forEach(
title -> this.booksList.save(Book.Builder().title(title).author("author of " + title).build())
);
This is being shown.
The method Builder() is undefined for the type Book
Try by Book b = new Book.builder()
.id(id).name(name).author("author of " + title).build();
May be you are trying to use title, which you haven't defined.
Your fields are:
private Long id
private String name
private String author
and in your builder constructor you put Book.Builder().title(title), the title is missing in the Book entity, a part "builder()" has to start with lowercase.
EDIT:
Ok, then you can remove #AllArgsConstructor, #NoArgsConstructor, #RequiredArgsConstructor and test it.

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

#JsonIgnore with #Getter Annotation

Can I use #JsonIgnore with #Getter annotation from lombok without explicitly define the getter, because I have to use this JsonIgnore while serializing the object but while deserializing, the JsonIgnore annotation must be ignored so the field in my object must not be null?
#Getter
#Setter
public class User {
private userName;
#JsonIgnore
private password;
}
I know, just by define the JsonIgnore on the getter of password I can prevent my password to be serialized but for that, I have to explicitly define the getter thing that I don't want.
Any idea please, Any help will be appreciated.
To put the #JsonIgnore to the generated getter method, you can use onMethod = #__( #JsonIgnore ). This will generate the getter with the specific annotation. For more details check
http://projectlombok.org/features/GetterSetter.html
#Getter
#Setter
public class User {
private userName;
#Getter(onMethod = #__( #JsonIgnore ))
#Setter
private password;
}
Recently i had the same issue using jackson-annotation 2.9.0 and lombok 1.18.2
This is what worked for me:
#Getter
#Setter
public class User {
#JsonIgnore
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
private String password;
So basically adding the annotation #JsonProperty(access = JsonProperty.Access.WRITE_ONLY) means that the property may only be written for deserialization (using setter) but will not be read on serialization (using getter)
This could be quite obvious but I lost a lot of time not thinking this solution before:
#Getter
#Setter
public class User {
private userName;
#Setter
private password;
#JsonIgnore
public getPassword() { return password; }
}
As Sebastian said #__( #JsonIgnore ) can resolve this issue but sometimes the use of the onX Lombok feature (#__()) can have side-effects for example breaking the javadoc generation.
I recently had the same issue.
There are several ways to solve it:
Create file lombok.config in the root folder of your project with content:
// says that it's primary config (lombok will not scan other folders then)
config.stopBubbling = true
// forces to copy #JsonIgnore annotation on generated constructors / getters / setters
lombok.copyableAnnotations += com.fasterxml.jackson.annotation.JsonIgnore
...
and in your class you can use this annotation as usual, on the field level:
#JsonIgnore
private String name;
Note: if you use lombok #RequiredArgsConstructor or #AllArgsConstructor, then you should remove all usages of #JsonIgnore with #JsonIgnoreProperties (as described in solution #4, or you may still choose solution #2 or #3). This is required because #JsonIgnore annotation is not applicable for constructor arguments.
Define Getters / Setters manually + add #JsonIgnore annotation on them:
#JsonIgnore
public String getName() { return name; }
#JsonIgnore
public void setName(String name) { this.name = name; }
Use #JsonProperty (it's either read-only or write-only, but no both):
#JsonProperty(access = JsonProperty.Access.READ_ONLY) // will be ignored during serialization
private String name;
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY) // will be ignored during deserialization
private String name;
Use #JsonIgnoreProperties({ "fieldName1", "fieldName2", "..."})
I personally use solution #1 globally and solution #4 for exceptions when class also has annotations #AllArgsConstructor or #RequiredArgsConstructor.
With JDK version 8 use below:
// #Getter(onMethod=#__({#Id, #Column(name="unique-id")})) //JDK7
// #Setter(onParam=#__(#Max(10000))) //JDK7
#Getter(onMethod_={#Id, #Column(name="unique-id")}) //JDK8
#Setter(onParam_=#Max(10000)) //JDK8
Source : https://projectlombok.org/features/experimental/onX

Categories