Contribute to help us improve!
Are there edge cases or problems that we didn't consider? Is there a technical pitfall that we should add? Did we miss a comma in a sentence?
If you have any input for us, we would love to hear from you and appreciate every contribution. Our goal is to learn from projects for projects such that nobody has to reinvent the wheel.
Let's collect our experiences together to make room to explore the novel!
To contribute click on Contribute to this page on the toolbar.
JPA
General conventions
Spring Data JPA with Hibernate
-
The standard for accessing relational databases in Java is the Java Persistence API (JPA).
-
Hibernate is the has proven to be a reliable implementation of JPA.
-
Use the JPA where ever possible and use vendor (hibernate) specific features only for situations when JPA does not provide a solution. In the latter case consider first if you really need the feature.
Entities
Entities must not contain business logic
Entities must be simple POJOs and not contain business logic.
Default columns
There’s a number of columns (attributes) each entity should contain:
Id specification
-
Add a
private Long id
-
The id needs to be annotated with
@Id
. -
The id uses database sequence for generation of the id.
@GeneratedValue(strategy = GenerationType.SEQUENCE)
. -
Use
Long
as type because it is the most efficient representation for the database. Therefore looking up a record and joins are faster compared to e.g.String
orUUID
.
Modification Counter
-
Add a
private int modificationCounter
. -
The modification counter is used as version of the entity for optimistic locking. Whenever the object gets modified and persisted this counter will be increased.
-
Use int for the modification counter.
-
Annotate the modification counter with
@Version
.
BLOBs must not be stored in byte arrays
Byte array will cause problems if BLOBs get large because the entire BLOB is loaded into the RAM. Use the datatype Blob. Stream the BLOB directly from the database to the user when the data is requested via an API.
Do not mix granularity of temporal values
-
Always use
TemporalType.TIMESTAMP
with@Temporal
. -
Mixing with other granularities causes problems when comparing one value to another.
Consider the fetch type of relations
When creating a relation between entities, the correct fetch type should be always considered for the use case. The difference between the two types eager and lazy loading is described here. Applications are strongly advised to use lazy loading, except it’s 100% clear that eager loading is necessary in all cases. Always override the default of the relationship annotation by providing the selected FetchType explicitly, to make it transparent for future developers.
Map enums to dedicated string
Mapping enums to their ordinal or name is fragile when it comes to code changes and refactoring. Define a dedicated string in each enum value for database representation. Enums then have to be treated as a custom datatype with a converter.
Entity inheritance
As a good practice it is recommended to avoid entity hierarchies at all where possible and otherwise to keep the hierarchy as small as possible.
If an hierarchy in the entities is needed, JPA defines several inheritance mechanisms, that are described here.
In most cases, single table
is a good choice, because it is usually the fastest way to do the mapping, as no joins are needed when retrieving, searching or persisting entities.
Moreover it is rather simple and easy to understand.
One major disadvantage is that the this approach can lead to huge tables with a lot of null values.
Accessing the data
Use Spring Respositories
For each entity «EntityName»Entity
an interface is created with the name «EntityName»Repository
extending JpaRepository
.
Use @Query
for simple custom queries
The @Query
annotation is used for easy cases where the needed results can be found by a simple SQL query.
In Quarkus, native and named queries via the @Query annotation are currently not supported. So in Quarkus fragments have to be used.
|
Use pagination
When dealing with large amounts of data, an efficient method of retrieving the data is required. Paging is used to process only small subsets of the entire data set.
Do not iterate over 1:n and n:m relations
When iterating over 1:n and n:m this most likely will end up in performance problems due to the N+1 problem. This means one query is used to fetch the entity and N queries are used to fetch the related entities.
To avoid the N+1 problem
-
create special functions to load the entity with the related entities
<EntityName>Repo.findBy<EntityName>With<RelatedEntityName>ById(id)
. This will solve the problem with one query. To do that join the tables of the entity and the related entities. -
load the related entities with the repository of the related entity
<RelatedEntityName>Repo.findBy<EntityName>Id(id)
. This will solve the problem with two queries if the entity and the related entities are needed and with one query if only the related entities are needed.
Security
Do not use string concatenation
Never build queries with string concatenation or your code might be vulnerable through SQL-Injections. Consider using Query parameters.