How do I create a safe Lombok JPA entity? - java

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

Related

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

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.).

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

Is there a configurable alternative to using #Version?

I'm placing an annotated field with #Version on it in all my JPA domain classes, however this just seems like additional boiler plate. Is there a way to get around this perhaps via configuration?
TIA,
Ole
As far as the JPA specification tells us you can't change the #Version annotation via "configuration". You either use #Version in your program code or you don't.
Referring to the official JPA specification (final version, JPA 2.1) in Section 3.4.2 (page 90) we find:
An entity is automatically enabled for optimistic locking if it has a property or field mapped with a Version mapping.
[...]
If only some entities contain version attributes, the persistence provider runtime is required to check those entities for which version attributes have been specified. The consistency of the object graph is not guaranteed, but the absence of version attributes on some of the entities will not stop operations from completing.
However, you can use the concept of inheritance to provide the #Versiononly in one spot via an abstract base class. This class you be written as follows:
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class AbstractBaseEntity {
public static final long INVALID_OBJECT_ID = -42;
#Version
private int version;
#Id
#SequenceGenerator(name = "sequence-object", sequenceName = "ID_MASTER_SEQ")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence-object")
#Column(name = "id")
protected Long objectID = INVALID_OBJECT_ID;
public final int getVersion() {
return version;
}
#Override
public long getObjectID() {
return objectID;
}
// ... maybe other methods or fields ...
}
Thus, all your #Entity annotated sub-classes that inherit from AbstractPersistentEntity are provided with both properties: (i) objectIDand (ii) version at once. For instance, class SomeClass can be written as:
#Entity
public class SomeClass extends AbstractBaseEntity /*implements SomeInterface*/ {
// ... specific methods or fields ...
}
For details on the use of #MappedSuperclass see also this answer.
Hope it helps.

How to create meta annotations on field level?

I have this hibernate class with annotations:
#Entity
public class SimponsFamily{
#Id
#TableGenerator(name = ENTITY_ID_GENERATOR,
table = ENTITY_ID_GENERATOR_TABLE,
pkColumnName = ENTITY_ID_GENERATOR_TABLE_PK_COLUMN_NAME,
valueColumnName = ENTITY_ID_GENERATOR_TABLE_VALUE_COLUMN_NAME)
#GeneratedValue(strategy = GenerationType.TABLE, generator = ENTITY_ID_GENERATOR)
private long id;
...
}
Since I don´t won´t to annotate every id field of my classes that way, I tried to create a custom anotation:
#TableGenerator(name = ENTITY_ID_GENERATOR,
table = ENTITY_ID_GENERATOR_TABLE,
pkColumnName = ENTITY_ID_GENERATOR_TABLE_PK_COLUMN_NAME,
valueColumnName = ENTITY_ID_GENERATOR_TABLE_VALUE_COLUMN_NAME)
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.FIELD)
public #interface EntityId {
#GeneratedValue(strategy = GenerationType.TABLE, generator = ENTITY_ID_GENERATOR)
public int generator() default 0;
#Id
public long id() default 0;
}
so that I can use this annotation in my class:
#Entity
public class SimponsFamily{
#EntityId
private long id;
...
}
I do have to write the #Id and the #GeneratedValue annotions on field level since they do not support the TYPE RetentionPolicy. This solutions seems to work.
My questions:
How are the field level annotations in my custom annotations(and values) transferred to my usage of EntityId annotation?
What about the default values which I set in my custom annotation, are they used since I do not specify attributes at the usage?
It is a preferred way to use annotations on field level in annotations?
I think I can aswer your third question.
One common way to do what you want (avoid duplicating ID mapping) is to create a common superclass that holds the annotated id and version (for optimistic locking) fields, and then have all persistent objects extend this superclass.
To ensure the superclass is not considered an Entity on its own, it must be annotated with #MappedSuperclass.
Here is a sample (sorry for typos, I don't have an IDE at hand right now) :
#MappedSuperclass
public class PersistentObject {
#Id // Put all your ID mapping here
private Long id;
#Version
private Long version;
}
#Entity
public class SimpsonsFamily extends PersistentObject {
// Other SimpsonFamily-specific fields here, with their mappings
}

Applying annotations to fields inherited from #MappedSuperclass

Has:
#MappedSuperclass
class Superclass {
#Id
#Column(name = "id")
protected long id;
#Column(name="field")
private long field;
}
and
#Entity
class Subclass extends Superclass {
}
How to annotate inherited id with #GeneratedValue and field with #Index within Subclass?
How to annotate inherited id with #GeneratedValue and field with #Index within Subclass?
AFAIK, you can't. What you can do is overriding attributes and associations (i.e. change the column or join column) using the AttributeOverride and AssociationOverride annotations. But you can't do exactly what you're asking.
For the GeneratedValue, consider using XML mapping to override the strategy if you don't want to declare it in the mapped superclass.
For the Index (which is not a standard annotation by the way), did you actually try to declare it at the table level using Hibernate's Table annotation instead (I'm assuming you're using Hibernate)?
#Table(appliesTo="tableName", indexes = { #Index(name="index1", columnNames=
{"column1", "column2"} ) } )
creates the defined indexes on the
columns of table tableName.
References
JPA 1.0 Specification
Section 2.1.9.2 "Mapped Superclasses"
Section 9.1.10 "AttributeOverride Annotation"
Section 9.1.11 "AttributeOverrides Annotation"
Section 9.1.12 "AssociationOverride Annotation"
Section 9.1.13 "AssociationOverrides Annotation"
Hibernate Annotations Reference Guide
2.4. Hibernate Annotation Extensions
Chapter 3. Overriding metadata through XML
As for #GeneratedValue, it is possible to do like this:
#MappedSuperclass
class Superclass {
#Id
#Column(name = "id")
#GeneratedValue(generator = "id_generator")
protected long id;
#Column(name = "field")
private long field;
}
#Entity
#SequenceGenerator(name = "id_generator", sequenceName = "id_seq")
class Subclass extends Superclass {
}
You might be able to do this if you apply the annotations to the accessor methods instead. (I haven't tried this, so I can't guarantee that it'll work.)
#MappedSuperclass
public class Superclass {
#Id
#Column(name = "id")
public long getId() {
return id;
}
.
#Entity
public class Subclass extends Superclass {
#GeneratedValue
public long getId() {
return super.getId();
}
Just in case anyone else searches for this, I used the following code which adds in some overhead, but for processing Field annotations only shouldn't add that much:
private List<Field> getAllFields() {
List<Field> fieldList = new ArrayList<Field>();
// Add all fields from the current class
fieldList.addAll(Arrays.asList(mElement.getClass().getDeclaredFields()));
// Use an index to iterate over mElement's parent types
Class clazz = mElement.getClass();
// Get any fields from the parent class(es)
while (clazz.getSuperclass() != null) {
fieldList.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields()));
// Set it to that parent class
clazz = clazz.getSuperclass();
}
return fieldList;
}
The returned list would contain all fields for all parent and child classes with mElement being the object you are searching for annotations from. Hope this helps.

Categories