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.

Naming conventions

  • Entities should be named <EntityName>Entity

  • Repositories should be named <EntityName>Repository

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 or UUID.

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

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.

If you are using Spring Data repositories you will get pagination support out of the box by providing the interfaces Page and Pageable.

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.

Use limited permissions

Operate the application with a database user that has limited permissions so the user can not modify the SQL schema (e.g. drop tables). For initializing the schema (DDL) or to do schema migrations use a separate user that is not used by the application itself.